Loki::SmartPtr
Loki名字是北欧神话里的火神洛基。Loki身出名门,是《Modern C++ Design》的作者Andrei Alexandresc贡献的一个C++通用库,包括typelist,functor,singleton,smart pointer,object factory,visitor和multimethods。网络评论说Loki过于华丽,很多特性现在并不适用。
回到智能指针上来,Loki的智能指针采用一种完全不同的设计策略。如果Boost是人族,那么Loki让你变成神族。Boost为了满足各种需求,设计出了多个智能指针。为什么要那么多呢?乔布斯要求苹果音乐市场的音乐都卖1美元,那能不能一个指针指针满足所有需求呢?
Loki说可以。Loki的智能指针方案采用基于策略的设计,将智能指针进行分解,分解为一些独立的功能模板,然后通过混合搭配的策略定制自己的智能指针。
template<typename T, template <class>class OwnershipPolicy =RefCounted, class ConversionPolicy =DisallowConversion, template <class>class CheckingPolicy =AssertCheck, template <class>class StoragePolicy =DefaultSPStorage> class SmartPtr;
我们可以看到,除了SmartPtr 所指向的对象类型T 以外,在模板类SmartPtr 中包括了这样一些策略:OwnershipPolicy(所有权策略)、ConversionPolicy(类型转换策略)、CheckingPolicy(检查策略)、StoragePolicy(存储策略)。正是通过这样的分解,使得SmartPtr 具备了极大的灵活性。我们可以任意组合各种不同的策略,从而获得不同的智能指针实现。下面先对各个策略逐一进行介绍:
除了Loki 已经定义的策略,你还可以自行定义策略。实际上,Loki 的智能指针模板覆盖了四种基本的Boost 智能指针类型:scoped_ptr、scoped_array、shared_ptr 和shared_array;至于weak_ptr,也可以通过定义相应的策略来实现其等价物。通过即将成为C++标准(C++0x)的typedef 模板特性,我们还可以利用Loki 的SmartPtr 模板来直接定义前面提到的Boost 的前四种智能指针类型。
鉴于神族实现比较复杂,有兴趣的可以读读源码。由于Loki是基于C++标准编写的,而现在的编译器对标准的支持有限,所以不要期望自己能够编译通过并使用。可以参考侯捷调通一个版本。
管理数组
数组的申请和释放不同单个对象,new和delete都带有[],来告诉编译器现在申请和释放的是一个数组资源。同样智能指针也需要一种提示,让它知道现在托管的是一个数组资源,要按数组的方式来申请和释放资源。
Boost提供了scoped_array和shared_array来为数组对象提供智能指针。它们分别是scoped_ptr和scoped_array的数组版本,实现上大同小异。
常见陷阱
1 代码中出现 delete 关键字或 C 语言的 free 函数。
Ø 智能指针帮助管理和释放资源,代码中delete会导致智能指针悬空,造成程序崩溃。对于选用智能指针的程序,不能出现delete和free。避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放。
2 在需要访问 Boost::shared_ptr 对象,而又不想改变其引用计数的情况下,使用 Boost::weak_ptr。
Ø Boost::weak_ptr
3 使用 std::auto_ptr,小心所有权转移。
Ø auto_ptr的拷贝构造函数,赋值函数都存在所有权转移。原指针在所有权转移后被置空,访问空指针造成程序崩溃。
4 派生类所对应的std::auto_ptr对象不能转换为基类所对应的std::auto_ptr对象。
Ø 这是一个常见问题,其他类型的智能指针也可能存在这个问题。最简单的解决版本是提供对象的派生类到基类的隐式类型转换,但这样的设计过于丑陋。这个问题可以归纳为:只要T*能隐式转换成U*,那么smart_ptr<T>就应该能隐式转换成smart_ptr<U>。可以考虑利用C++的成员函数模板(Member Function Template)实现。shared_ptr支持派生类到基类的转换。
5 使用shared_ptr所带来的循环引用而造就的内存泄露。
Ø 循环引用会额外增加引用,在shared_ptr释放时,引用计数不会清零,托管的资源不会被释放。(这点是其它各种引用计数管理内存方式的通病)。
6 构造一个临时的shared_ptr作为函数的参数。
Ø 这个问题与异常和函数参数的求值顺序有关。C++构造函数发生的异常将不会调用析构函数。例如f(shared_ptr<int>(new int(2)), g())。构造了临时的 shared_ptr ,这就为内存泄漏留下了可乘之机。因为函数参数的求值顺序是不确定的,new int(2) 首先被求值,g() 第二个是有可能的,如果 g 抛出一个异常,我们永远也不可能到达 shared_ptr 的构造函数。new int(2)申请的资源没有机会被释放。
Ø 关于这类问题有一个很好的准则:“Perform every resource allocation (e.g., new) in its own code statement which immediately gives the new resource to a manager object (e.g., auto_ptr)”。详细的分析介绍可以参考《More Exceptional C++》。
7 不要一个原始指针被shared_ptr管理后,不要再把这个原始指针交给另外一个智能指针管理。
Ø 一个资源被托管给多个智能指针,释放时存在多次释放造成程序崩溃的风险。
参考文献
[] http://www.360doc.com/content/10/1215/13/3705007_78340485.shtml
[] http://dozb.bokee.com/1976635.html
[] http://xuchaoqian.com/?p=797
[] http://blog.csdn.net/wang_jing_2008/article/details/7924739
[] http://www.drdobbs.com/move-constructors/184403855, Move Constructor
[] http://blog.csdn.net/pongba/article/details/1697636
[] Modern C++ Design