大多数情况下,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
是相同的 flyweight 类型这一事实导致这两类的值存储在同一个 flyweight 工厂中,尽管它们的各自范围预计不会重叠。标记 可以用来将它们变成真正不同的类型
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
是不同的 flyweight 类,每个类都有单独的工厂。标记是一种纯粹的语法手段:任何类型都可以用于 tag
构造内部的标记,尽管良好的风格建议使用描述性名称的标记类,这些标记类是 flyweight 类型定义上下文的本地类。
flyweight
使用一种称为工厂的内部组件,其目的是存储和检索 flyweight 对象在给定时间引用的不同值。默认情况下,使用基于哈希容器的工厂,因此 flyweight<T>
实际上等同于
flyweight<T,hashed_factory<> >
其中 hashed_factory
是所谓的工厂说明符。Boost.Flyweight 提供了几个预定义的工厂说明符,它们不仅允许用户选择使用的工厂的具体类型,还接受它们自己的模板参数来定制每个工厂。
给定的 flyweight
实例具有关联的 flyweight::key_type
和 flyweight::value_type
类型(在常规 flyweight 的情况下它们相等,如果使用键值 flyweight,则它们不同)。此外,还有一个内部 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
是 flyweight 的键类型,boost::mpl::_1
如上所述,代表工厂中存储元素的内部 Entry
类型。假设我们想要为 std::string
flyweight 配置带有特殊哈希谓词 special_hash
和自定义分配器 custom_allocator
的 hashed_factory
;这将按如下方式指定
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 中的并发容器的工厂。该工厂特别适用于多线程场景中的 flyweight 创建,因为它不需要外部同步或跟踪;因此,它通常应与 no_locking
和 no_tracking
结合使用
typedef flyweight< std::string, concurrent_factory<>, no_locking, no_tracking > concurrent_string_flyweight;
未使用的值(那些不再被任何 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
类型的有序容器来实现 flyweight 工厂。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 类型,即类模板 flyweight
的每个不同实例,都与一个工厂对象相关联。在大多数情况下,此工厂对象是如何创建的对于 Boost.Flyweight 的用户来说并不重要,但是在某些特殊情况下,控制这方面是必要的。一个名为holder的内部组件负责实例化工厂类和一些其他内部信息;此组件由holder 说明符指定,static_holder
是默认的 holder 说明符。
static_holder
头文件:"boost/flyweight/static_holder.hpp"
语法:static_holder
这是 Boost.Flyweight 的默认 holder 说明符,并生成 holder,其中唯一的工厂作为程序的本地静态变量存在。
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
使用的 flyweight 工厂对象与主程序中看到的工厂对象不是同一个工厂对象:因此,str1
和 str2
在内部指向的值表示将不同,并且会被错误地认为不相等。由于工厂重复,可能会出现许多其他问题,包括未定义的行为。
intermodule_holder
指定一个工厂 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 复制副本)