大多数情况下,flyweight
的默认配置就足够好了,用户无需关心对其 flyweight
实例的进一步调整;然而,当需要更多控制 Boost.Flyweight 的行为时,提供了全面的机制来选择、配置甚至扩展以下实现方面
flyweight
对象引用的共享值。flyweight
类模板具有一个“智能”规范接口,通过该接口,配置方面可以作为可选的模板参数以用户喜欢的任何顺序提供。例如,可以使用 基于集合的工厂和 不跟踪的 std::string
的标记 flyweight
可以这样指定
flyweight<std::string, tag<label_t>, set_factory<>, no_tracking >
或者这样
flyweight<std::string, no_tracking, tag<label_t>, set_factory<> >
或者以任何其他顺序;只有 std::string
需要占据规范的第一个位置。
介绍部分中显示的示例代码使用 "boost/flyweight.hpp"
便捷头文件,它只包含类模板 flyweight
及其默认配置组件的头文件
#include <boost/flyweight/flyweight.hpp> // class template flyweight #include <boost/flyweight/hashed_factory.hpp> // hashed flyweight factory #include <boost/flyweight/static_holder.hpp> // regular factory instantiation #include <boost/flyweight/simple_locking.hpp> // simple locking policy #include <boost/flyweight/refcounted.hpp> // refcounting tracking policy
当使用这些组件以外的组件时,必须显式包含其特定头文件。
考虑以下两种类型
typedef flyweight<std::string> name_t; typedef flyweight<std::string> ip_address_t;
虽然技术上这两种类型是相同的,但这仅仅是因为巧合,因为名称和 IP 地址之间没有明确的关系。在内部,name_t
和 ip_address_t
是相同的享元类型,导致这两个类的值存储在同一个享元工厂中,尽管它们各自的范围预计不会重叠。标记可以用来将它们变成真正不同的类型
struct name_tag{}; typedef flyweight<std::string,tag<name_tag> > name_t; struct ip_address_tag{}; typedef flyweight<std::string,tag<ip_address_tag> > ip_address_t;
现在,name_t
和 ip_address_t
是不同的享元类,它们各自有单独的工厂。标记是一种纯粹的语法设备:任何类型都可以用于 tag
结构中的标记,尽管良好的风格建议使用具有描述性名称的标记类,这些名称对于正在定义享元类型的上下文是本地的。
flyweight
使用一种名为工厂的内部组件,其目的是存储和检索享元对象在给定时间引用的不同值。默认情况下,使用基于哈希容器的工厂,因此 flyweight<T>
实际上等效于
flyweight<T,hashed_factory<> >
其中 hashed_factory
是一个所谓的工厂指定符。Boost.Flyweight 提供了几个预定义的工厂指定符,它们不仅允许用户选择所使用的特定工厂类型,还接受它们自己的模板参数来定制每个工厂。
给定的 flyweight
实例具有关联的 flyweight::key_type
和 flyweight::value_type
类型(在常规享元的情况下它们相等,如果使用 键值享元,则它们不同)。此外,还有一个内部 Entry
类型,它对应于实际存储在工厂中的对象的类型:Entry
包含 flyweight
的共享 value_type
对象以及一些内部簿记信息;此外,Entry
隐式转换为 const key_type&
,以便工厂可以依赖 key_type
来查找 Entry
。由于 Entry
是 flyweight
实现的内部类型,因此用户无法在工厂的配置中直接引用它。相反,可以使用代理 占位符 类型 boost::mpl::_1
。
hashed_factory
头文件:"boost/flyweight/hashed_factory.hpp"
语法:hashed_factory<[Hash[,Pred[,Allocator]]]>
这个指定符是 Boost.Flyweight 默认采用的,它控制对内部基于哈希容器的工厂的使用。通过 BinaryPredicate
Pred
来确定值的等效性,并使用 Hash
将值索引到工厂容器中,Hash
假设是类型为 Key
的参数的 Hash
函数对象。Allocator
参数由工厂容器用于其内存分配需求。这些参数的默认类型使得表达式
flyweight<T,hashed_factory<> >
等效于
flyweight< T, hashed_factory< boost::hash<key_value>, std::equal_to<key_value>, std::allocator<boost::mpl::_1> > >
其中 key_type
是享元的键类型,如上所述,boost::mpl::_1
代表存储在工厂中的元素的内部 Entry
类型。假设我们想为一个 std::string
享元配置 hashed_factory
,使用特殊的哈希谓词 special_hash
和自定义分配器 custom_allocator
;这将被指定如下
flyweight< std::string, hashed_factory< special_hash<std::string>, std::equal_to<key_value>, custom_allocator<boost::mpl::_1> > >
concurrent_factory
头文件:"boost/flyweight/concurrent_factory.hpp"
语法:concurrent_factory<[Hash[,Pred[,Allocator]]]>
此指定符提供一个基于 Boost.Unordered 中并发容器的工厂。该工厂特别适合在多线程场景中创建享元,因为它不需要外部同步或跟踪;因此,它通常应与 no_locking
和 no_tracking
一起使用
typedef flyweight< std::string, concurrent_factory<>, no_locking, no_tracking > concurrent_string_flyweight;
未使用的值(那些不再被任何享元引用的值)会通过内置的垃圾回收器定期从工厂中擦除。
concurrent_factory
参数的默认类型使得表达式
flyweight<T,concurrent_factory<> >
等效于
flyweight< T, concurrent_factory< boost::hash<key_value>, std::equal_to<key_value>, std::allocator<boost::mpl::_1> > >
与 hashed_factory
中的含义相同。
set_factory
头文件:"boost/flyweight/set_factory.hpp"
语法:set_factory<[Compare[,Allocator]]>
set_factory
采用类似 std::set
的有序容器来实现享元工厂。Compare
必须是一个 BinaryPredicate
,它在 flyweight
作用的值类型上强制执行严格的弱排序;与 STL 有序容器的习惯一样,如果根据 Pred
,两个值都不小于另一个值,则认为这两个值等效。Allocator
是一个分配器类型,它被传递给工厂内部容器以用于其内存相关任务。当使用默认参数时,表达式
flyweight<T,set_factory<> >
等效于
flyweight< T, set_factory<std::less<key_type>,std::allocator<boost::mpl::_1> > >
在选择 set_factory
和 hashed_factory
时,有序容器和哈希容器的比较中出现的常见权衡也适用:因此,基于集合的查找和值插入通常比基于哈希的查找和值插入慢,但后者可能会受到性能非常差的病态最坏情况的影响。
assoc_container_factory
头文件:"boost/flyweight/assoc_container_factory.hpp"
语法:assoc_container_factory<ContainerSpecifier>
这个指定符可以看作是 hashed_factory
和 set_factory
的概括,用户可以在其中提供工厂所基于的容器的确切类型。对于那些不熟悉 Boost MPL 库的人来说,指定容器的方式起初可能看起来有点令人生畏:ContainerSpecifier
必须是一个 MPL Lambda Expression
,当使用 上面解释的 Entry
和 key_type
类型调用时,它会生成一个 Entry
元素的容器类型,满足以下要求
AssociativeContainer
或 UnorderedAssociativeContainer
的模型,其中 Entry
的等效性由条目可转换到的 key_type
值确定。让我们通过一个例子来看看容器指定符是什么样的。假设我们有自己的有序容器,如下所示
template< typename Elem, typename Compare=std::less<Elem>, typename Allocator=std::allocator<Elem> > class ultrafast_set { ... };
然后,可以像这样将 ultrafast_set
插入到 assoc_container_factory
中
typedef flyweight< std::string, assoc_container_factory< // MPL lambda expression follows ultrafast_set<mpl::_1,std::less<std::string> > > > flyweight_string;
正如已经解释的那样,mpl::_1
是一个所谓的 MPL 占位符,作为 Boost.Flyweight 内部机制替换为 Entry
的“槽”。请注意,我们没有依赖 ultrafast_set
的 Compare
的默认参数,而是为 std::string
提供了固定的实例化:这是因为要求声明,内部填充 ContainerSpecifier
的类型可以转换为 const key_type&
(此处为 const std::string&
),并且应基于 key_type
来确定条目的查找和等效性。另一方面,Allocator
参数的默认参数可以正常工作,如果我们将其显式写出来,则会更明显
typedef flyweight< std::string, assoc_container_factory< ultrafast_set< mpl::_1, std::less<std::string>, std::allocator<mpl::_1> > > > flyweight_string;
每个享元类型,即类模板 flyweight
的每个不同的实例化,都与一个工厂对象相关联。在大多数情况下,此工厂对象的创建方式对于 Boost.Flyweight 的用户来说并不重要,但在某些特殊情况下,有必要控制这方面。一个名为持有者的内部组件负责实例化工厂类和一些其他内部信息;此组件通过持有者指定符来规定,static_holder
是默认的指定符。
static_holder
头文件:"boost/flyweight/static_holder.hpp"
语法:static_holder
这是 Boost.Flyweight 的默认持有者指定符,它生成的持有者,其唯一的工厂作为程序的局部静态变量存在。
intermodule_holder
头文件:"boost/flyweight/intermodule_holder.hpp"
语法:intermodule_holder
在大多数 C++ 环境中,静态变量不能很好地与动态加载的模块混合,因为相同静态变量的实例可以在不同的模块中重复,即使根据定义,该变量应该是唯一的。在许多情况下,如果模块不使用受影响的类型相互通信,则这种重复不会引起注意,但请考虑以下发生这种通信的情况
// module 1 typedef flyweight<std::string> flyweight_string; // produce_string is exported so that it can be dynamically // linked flyweight_string produce_string() { return flyweight_string("boost"); }
// main program typedef flyweight<std::string> flyweight_string; int main() { ... // import module 1 flyweight_string str1=produce_string(); flyweight_string str2("boost"); assert(str1==str2); }
在许多环境中,此程序会导致断言失败,因为模块 1 中看到的 flyweight_string
使用的享元工厂对象与主程序中看到的工厂对象不同:因此,str1
和 str2
内部指向的值表示形式将不同,并且会被错误地认为不相等。由于工厂重复,可能会出现许多其他问题,包括未定义的行为。
intermodule_holder
指定一个工厂持有者,它可以避免重复问题,并确保程序的所有模块都使用相同的工厂实例。为了修复上面的例子,只需在两个模块中将 flyweight_string
重新定义为
typedef flyweight<std::string,intermodule_holder> flyweight_string;
intermodule_holder
在编译时间方面比 static_holder
要繁琐得多,并且在程序启动时会引入不可忽略的开销,因此其使用应仅限于真正必要的情况。
与每个 flyweight
类型关联的内部工厂是一个共享资源,因此必须在多线程环境中正确同步对其的访问。锁定策略 指定用于此目的的同步机制。
simple_locking
头文件:"boost/flyweight/simple_locking.hpp"
语法:simple_locking
这是默认的锁定策略。它指定操作系统提供的最简单的原生同步原语(如果可用)。
no_locking
头文件:"boost/flyweight/no_locking.hpp"
语法:no_locking
不强制同步,因此允许对实现共享资源进行不受限制的内部访问。选择 no_locking
会比默认的 simple_locking
执行速度稍快,但它会使类型变为线程不安全,这可能会导致灾难性的后果。除非在单线程环境或绝对保证特定的 flyweight
类型不会在并发场景中使用,否则不应使用此策略。
跟踪策略 控制 flyweight
对象的生命周期,并可以基于此信息进行操作。例如,合适的跟踪机制可以确定何时可以安全地擦除工厂中存储的给定值,因为它不再被任何 flyweight
引用;这正是默认跟踪策略 refcounted
所做的。
refcounted
头文件:"boost/flyweight/refcounted.hpp"
语法:refcounted
此跟踪策略确定工厂中存储的值配备引用计数机制,以便在与该值关联的最后一个 flyweight
对象被销毁时擦除工厂条目。
no_tracking
头文件:"boost/flyweight/no_tracking.hpp"
语法:no_tracking
选择此策略时不进行 flyweight 跟踪,这意味着工厂中存储的值会一直保留到程序终止。与 refcounted
相比,no_tracking
既有优点也有缺点。优点是
no_tracking
的潜在缺点包括修订于 2024 年 9 月 27 日
© 版权所有 2006-2024 Joaquín M López Muñoz。根据 Boost 软件许可 1.0 版分发。(请参阅随附文件 LICENSE_1_0.txt 或在 https://boost.ac.cn/LICENSE_1_0.txt 复制)