Boost中的智能指针撰文 Bjorn Karlsson 翻译 曾毅 最后更新:2004年6月2日 欢迎来到Boost,C++革新者出类拔萃的社群。如果你在C++标准库当中找不到你所需要的,很可能Boost已经为您准备好了他们的产品。
根据Boost网站的介绍,Boost是“一个免费的,可移植的,同步评测的C++库,Boost堪称是新类库的典范,特别是其中那些能够与ISO C++标准库良好的协同工作的库。”但是Boost不仅仅是一个库的集合。它也是一个快速发展的开发者社区,这些开发者创建,使用以及参与讨论Boost 库。Boost社群不仅仅是维护着这个库,而且还为它的使用者和设计者提供学习交流的场所。这个库堪称是一个设计稳固类的精典范例,在下个版本发布之前你 甚至感觉不到能够有什么地方还值得改进。加入Boost邮件列表上的讨论组(或者是活跃于其中,或者只是看看别人如何讨论)是提高你对库的设计的问题和解 决方案的认识的非常好的方法。Boost还提供一个人数飞速增长的Boost使用者邮件列表,这个列表关注的内容集中在使用Boost库的问题上。 Boost库的质量和他的技术标准是十分令人惊异的。Boost可移植性标准确保了当你将你的代码从一个平台上移动到另一个平台上时,你的库仍然会正常工 作。最近的发布版本是Boost 1.25.0,由从智能指针到正则表达式,直至可移植的线程库。Boost目前支持35个库,这些当中所有的内容都被社区的成员测试和使用过了。这些库都 是可以免费使用的,它们当中的很多内容都已经被用于商业应用软件的开发。Boost是C++社群中最为强大的一个之一。在2000名成员当中,很多的人都是世界顶级的C++程序员。这些成员之所以能够长期的参与到其中 来是因为他们非常热爱同拥有最优秀的思维方法的程序设计者一同工作。他们同时很清楚他们的努力必定会对C++社群产生巨大的影响,因为你在Boost当中 看到的大部分内容将成为融入未来C++标准的候选内容。 了解Boost最好的方法就是浏览Boost库。在这篇文章当中,我将向你介绍Boost的智能指针(smart pointer)库smart_ptr。smart_ptr是一个能够反映出Boost的创新以及完美设计的好例子。我建议你访问Boost的站点()来 获取Boost集中的其它34个库的详细内容。
相对来说比较小的Boost库之一便是smart_ptr。smart_ptr是我认为在C++标准中将会停止向前发展的库之一。这篇文章讨论了Boost当中的smart_ptr库。但首先,我将以一个对智能指针的简介开始。
智能指针的30秒介绍智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。事实上,智能指针能够做的还有很多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务。有能够为这些ESP (Extremely Smart Pointers)创建一般智能指针的方法,但是并没有涵盖近来。(可以查看[1]来了解关于这个主题的更为深入的内容,顺便提一下, Alexandrescu现在正在考虑将他的C++库Loki提交给Boost)。 智能指针的大部分使用是用于生存期控制,阶段控制。它们使用operator->和operator*来生成原始指针,这样智能指针看上去就像一个普通指针。 这样的一个类来自标准库:std::auto_ptr。它是为解决资源所有权问题设计的,但是缺少对引用数和数组的支持。并且,std:: auto_ptr在被复制的时候会传输所有权。在大多数情况下,你需要更多的和/或者是不同的功能。这时就需要加入smart_ptr类。
smart_ptr 类在Boost中的智能指针有:。scoped_ptr,用于处理单个对象的唯一所有权;与std::auto_ptr不同的是,scoped_ptr可以被复制。 。scoped_array,与scoped_ptr类似,但是用来处理数组的 。shared_ptr,允许共享对象所有权 。shared_array,允许共享数组所有权
scoped_ptrscoped_ptr智能指针与std:: auto_ptr不同,因为它是不传递所有权的。事实上它明确禁止任何想要这样做的企图!这在你需要确保指针任何时候只有一个拥有者时的任何一种情境下都 是非常重要的。如果不去使用scoped_ptr,你可能倾向于使用std::auto_ptr,让我们先看看下面的代码:auto_ptr MyOwnString? (new string("This is mine to keep!")); auto_ptr NoItsMine?(MyOwnString?); cout << *MyOwnString << endl; // Boom
这段代码显然将不能编译通过,因为字符串的所有权被传给了NoItsMine。这不是std::auto_ptr的设计缺陷—而是一个特性。尽管如此,当你需要MyOwnString达到上面的代码预期的工作效果的话,你可以使用scoped_ptr: scoped_ptr MyOwnString? (new string("This is mine to keep for real!")); // Compiler error - there is no copy constructor. scoped_ptr TryingToTakeItAnyway? (MyOwnString?);
scoped_ptr通过从boost::noncopyable继承来完成这个行为(可以查看Boost.utility库)。不可复制类声明复制构造函数并将赋值操作符声明为private类型。
scoped_arrayscoped_array与scoped_ptr显然是意义等价的,但是是用来处理数组的。在这一点标准库并没有考虑—除非你当然可以使用std::vector,在大多数情况下这样做是可以的。用法和scoped_ptr类似: typedef tuples::tupleint> ArrayTuple?;
scoped_array MyArray?(new ArrayTuple?[10]); tuples::get<0>(MyArray?[5]) ="The library Tuples is also part of Boost";
tuple是元素的集合—例如两倍,三倍,和四倍。Tuple的典型用法是从函数返回多个值。Boost Tuple库可以被认为是标准库两倍的扩展,目前它与近10个tuple元素一起工作。支持tuple流,比较,赋值,卸包等等。更多关于Boost的 Tuple库的信息请参考[2]和[3]。 当scoped_array越界的时候,delete[]将被正确的调用。这就避免了一个常见错误,即是调用错误的操作符delete。
shared_ptr这里有一个你在标准库中找不到的—引用数智能指 针。大部分人都应当有过使用智能指针的经历,并且已经有很多关于引用数的文章。最重要的一个细节是引用数是如何被执行的—插入,意思是说你将引用计数的功 能添加给类,或者是非插入,意思是说你不这样做。Boost shared_ptr是非插入类型的,这个实现使用一个从堆中分配来的引用计数器。关于提供参数化策略使得对任何情况都极为适合的讨论很多了,但是最终讨 论的结果是决定反对聚焦于可用性。可是不要指望讨论的结果能够结束。shared_ptr完成了你所希望的工作:他负责在不使用实例时删除由它指向的对象(pointee),并且它可以自由的共享它指向的对象(pointee)。 void PrintIfString?(const any& Any) { if (const shared_ptr* s = any_cast >(&Any)) { cout << **s << endl; } } int main(int argc, char* argv[]) { std::vector Stuff; shared_ptr SharedString1? (new string("Share me. By the way, Boost.any is another useful Boost library")); shared_ptr SharedString2? (SharedString1?);
shared_ptr (new int(42));
shared_ptr (SharedInt1?); Stuff.push_back(SharedString1?); Stuff.push_back(SharedString2?); Stuff.push_back(SharedInt1?); Stuff.push_back(SharedInt2?); // Print the strings for_each(Stuff.begin(), Stuff.end(), PrintIfString?); Stuff.clear(); // The pointees of the shared_ptr‘s // will be released on leaving scope // shared_ptr的pointee离开这个范围后将被释放 return 0; }
any库提供了存储所有东西的方法[2]HYPERLINK "file:///C:Documents%20and%20SettingsAdministrator桌面 My%20Documents新建 CUJhtml20.04karlsson%22%20l"[4]。在包含类型中需要的是它们是可拷贝构造的(CopyConstructible),析 构函数这里绝对不能引发,他们应当是可赋值的。我们如何存储和传递“所有事物”?无区别类型(读作void*)可以涉及到所有的事物,但这将意味着将类型 安全(与知识)抛之脑后。any库提供类型安全。所有满足any需求的类型都能够被赋值,但是解开的时候需要知道解开类型。any_cast是解开由 any保存着的值的钥匙,any_cast与dynamic_cast的工作机制是类似的—指针类型的类型转换通过返回一个空指针成功或者失败,因此赋值 类型的类型转换抛出一个异常(bad_any_cast)而失败。
shared_arrayshared_array与shared_ptr作用是相同的,只是它是用于处理数组的。shared_array MyStrings?( new Base[20] );
深入shared_ptr实现创建一个简单的智能指针是非常容易的。但是创建一个能够在大多数编译器下通过的智能指针就有些难度了。而创建同时又考虑异常安全就更为困难了。 Boost::shared_ptr这些全都做到了,下面便是它如何做到这一切的。(请注意:所有的include,断开编译器处理,以及这个实现的部分 内容被省略掉了,但你可以在Boost.smart_ptr当中找到它们)。 首先,类的定义:很显然,智能指针是(几乎总是)模板。 template class shared_ptr {
公共接口是: explicit shared_ptr(T* p =0) : px(p) { // fix: prevent leak if new throws try { pn = new long(1); } catch (...) { checked_delete(p); throw; } }
现在看来,在构造函数当中两件事情是容易被忽略的。构造函数是explicit的,就像大多数的构造函数一样可以带有一个参数。另外一个值 得注意的是引用数的堆分配是由一个try-catch块保护的。如果没有这个,你得到的将是一个有缺陷的智能指针,如果引用数没有能够成功分配,它将不能 正常完成它自己的工作。 ~shared_ptr() { dispose(); }
析构函数执行另外一个重要任务:如果引用数下降到零,它应当能够安全的删除指向的对象(pointee)。析构函数将这个重要任务委托给了另外一个方法:dispose。 void dispose() { if (—*pn == 0) { checked_delete(px); delete pn; } }
正如你所看到的,引用数(pn)在减少。如果它减少到零,checked_delete在所指对象 (px)上被调用,而后引用数(pn)也被删除了。 那么,checked_delete执行什么功能呢?这个便捷的函数(你可以在Boost.utility中找到)确保指针代表的是一个完整的类型。在你的智能指针类当中有这个么? 这是第一个赋值运算符:
template (const shared_ptr& r) { share(r.px,r.pn); return *this; }
这是成员模版,如果不是这样,有两种情况: 1. 如果没有参数化复制构造函数,类型赋值Base = Derived无效。 2. 如果有参数化复制构造函数,类型赋值将生效,但同时创建了一个不必要的临时smart_ptr。 这再一次的展示给你为什么不应当加入你自己的智能指针的一个非常好的原因—这些都不是很明显的问题。 赋值运算符的实际工作是由share函数完成的: void share(T* rpx, long* rpn) { if (pn = rpn) { // Q: why not px = rpx? // A: fails when both == 0 ++*rpn; // done before dispose() in case // rpn transitively dependent on // *this (bug reported by Ken Johnson) dispose(); px = rpx; pn = rpn; } }
需要注意的是自我赋值(更准确地说是自我共享)是通过比较引用数完成的,而不是通过指针。为什么这样呢?因为它们两者都可以是零,但不一定是一样的。
template (const shared_ptr& r) : px(r.px) { // never throws ++*(pn = r.pn); }
这个版本是一个模版化的拷贝构造和函数。可以看看上面的讨论来了解为什么要这样做。 赋值运算符以及赋值构造函数在这里同样也有一个非模版化的版本: shared_ptr(const shared_ptr& r) : // never throws px(r.px) { ++*(pn = r.pn); } shared_ptr& operator= (const shared_ptr& r) { share(r.px,r.pn); return *this; }
reset函数就像他的名字那样,重新设置所指对象(pointee)。在将要离开作用域的时候,如果你需要销毁所指对象(pointee)它将非常方便的帮你完成,或者简单的使缓存中的值失效。 void reset(T* p=0) { // fix: self-assignment safe if ( px == p ) return; if (—*pn == 0) { checked_delete(px); } else { // allocate new reference // counter // fix: prevent leak if new throws try { pn = new long; } catch (...) { // undo effect of —*pn above to // meet effects guarantee ++*pn; checked_delete(p); throw; } // catch } // allocate new reference counter *pn = 1; px = p; } // reset
这里仍然请注意避免潜在的内存泄漏问题和保持异常安全的处理手段。 这样你就有了使得智能指针发挥其“智能”的运算符: // never throws T& operator*() const { return *px; } // never throws T* operator->() const { return px; } // never throws T* get() const { return px; }
这仅仅是一个注释:有的智能指针实现从类型转换运算符到T*的转换。这不是一个好主意,这样做常会使你因此受到伤害。虽然get在这里看上去很不舒服,但它阻止了编译器同你玩游戏。 我记得是Andrei Alexandrescu说的:“如果你的智能指针工作起来和哑指针没什么两样,那它就是哑指针。”简直是太对了。 这里有一些非常好的函数,我们就拿它们来作为本文的结束吧。 long use_count() const { return *pn; } // never throws bool unique() const { return *pn == 1; } // never throws
函数的名字已经说明了它的功能了,对么? 关于Boost.smart_ptr还有很多应当说明的(比如std::swap和std::less的特化,与std:: auto_ptr榜定在一起确保兼容性以及便捷性的成员,等等),由于篇幅限制不能再继续介绍了。详细内容请参考Boost distribution ()的smart_ptr.hpp。即使没有那些其它的内容,你不认为他的确是一个非常智能的指针么?
总结这仅仅是Boost世界的一个简短的介绍。欢迎每一个人的加入,坦率地说,我认为 大多数的C++程序员有很好的理由这样做。我在这里要对Beman Dawes, David Abrahams, 以及Jens Maurer回答问题以及分享他们观点的帮助表示感谢(参见“ 来自 Boost创始人的回答 ” )。Boost见!
注释与参考[1] Andrei Alexandrescu. Modern C++ Design (Addison-Wesley, 2001).[2] Boost, . [3] Jaako Jarvi. “Tuple Types and Multiple Return Values,” C/C++ Users Journal, August 2001. [4] Jim Hyslop和Herb Sutter. “I‘d Hold Anything for You,” C/C++ Users Journal C++ Experts Forum, December 2001, . [5] Boost邮件列表, . [6] Boost用户邮件列表, . [7] C++ Standard, International Standard ISO/IEC 14882. 作者简介 Bjorn Karlsson是ReadSoft专业的软件开发者,您可以通过下面的邮件与他取得联系:
译者注[1]截至本文翻译结束,Boost社群发布的最新版本为Boost 1.31.0版,最新的版本发布信息可以通过下列地址了解:http://lists./MailArchives/boost-announce/msg00034.php http:///project/shownotes.php?release_id=214915 同时可以通过下面的地址下载所有的Boost发布版本: http:///project/showfiles.php?group_id=7586 [2]截至本文翻译结束,Boost库已经扩展到了55个,所有的库文档及其它资源可以通过下列地址获得: http://boost./libs/libraries.htm [3]文中结尾处提到的“来自 Boost创始人的回答 ” 可以在下面的地址找到: http://www./documents/s=8470/cuj0204karlsson/side1.htm
|
|