![]() |
|
||||||||||||||
| | 网站首页 | 数据库教程 | web编程 | 服务器 | 程序设计 | | ||
|
||
|
|||||
| 智能指针的标准之争:Boost vs. Loki | |||||
作者:佚名 文章来源:不详 点击数: 更新时间:2007-9-12 ![]() |
|||||
|
智能指针的标准之争:Boost vs. Loki 撰文/马维达 weida@flyingdonkey.com 2001 年10 月和2002 年4 月,在美国的华盛顿和荷兰的安的列斯群岛上分别召开了两次C++标准会议。 议的内容之一是对一项新的C++特性提议——智能指针(Smart Pointer)——进行讨论。本文将对可能成为C ++新标准的两种智能指针方案(Boost vs. Loki)进行介绍和分析,并给出了相应的使用实例。 关键词:智能指针 C++ Boost Loki 在现在的标准C++中,只有一种智能指针:std::auto_ptr。其原因并非是因为auto_ptr 已足以应付所 有相关的工作——实际上,auto_ptr 有一个重大的缺陷,就是它不能被用在STL 容器中——而是因为现在的C+ +标准在制定时并未能对智能指针进行全面的考察。按照C++标准委员会成员Herb Sutter 的说法,只有一种标 准的智能指针是一件“可羞”的事情:首先,智能指针所能做的许多有用的事情,是可怜的auto_ptr 不能完成 的;其次,在有些情况下使用auto_ptr 可能会造成问题,上面所说的不能在容器中使用就是一例。实际上,许 多程序员已经开发了各种有用的智能指针,有些甚至在auto_ptr 被定为标准之前就已存在,但问题是,它们不 是标准的。在这样的情况下,C++标准委员会考虑引入新的智能指针,也就是自然而然的事情了。目前进入委员会 视野的,主要有两种智能指针方案:Boost 智能指针和Loki 智能指针。前者是由C++标准委员会库工作组发起的 Boost 组织开发的,而后者由世界级的C++专家Andrei Alexandrescu 开发,并在他所著的“Modern C++ Design”一书中进行了详细的阐释。下面,让我们分别来看一看这两种方案各自的技术特点。 一、 Boost 智能指针 Boost 的智能指针方案实现了五种智能指针模板类,每种智能指针都用于不同的目的。这五种智能指针是: template<typename T> class scoped_ptr; template<typename T> class scoped_array; template<typename T> class shared_ptr; template<typename T> class shared_array; template<typename T> class weak_ptr; 下面将分别介绍它们各自的特性,并给出相应的使用实例: ?? scoped_ptr:意在用作指向自动(栈)对象的、不可复制的智能指针。该模板类存储的是指向动态分配的 对象(通过new 分配)的指针。被指向的对象保证会被删除,或是在scoped_ptr 析构时,或是通过显式地调用 reset 方法。注意该模板没有“共享所有权”或是“所有权转让”语义。同时,它也是不可复制的(noncopyab le)。正因为如此,在用于不应被复制的指针时,它比shared_ptr 或std:auto_ptr 要更安全。与auto_ptr 一样,scoped_ptr 也不能用于STL 容器中;要满足这样的需求,应该使用shared_ptr。另外,它也不能用于 存储指向动态分配的数组的指针,这样的情况应使用scoped_array。 下面是使用scoped_ptr 的一个简单实例: class CTest { public: CTest() : m_id(0) {} CTest(int id) : m_id(id) {} ~CTest() { std::cout << "id: " << m_id << " - Destructor is being called\n"; } void SetId(int id) { m_id = id; } int GetId() { return m_id; } void DoSomething() { std::cout << "id: " << m_id << " - Doing something\n"; } private: int m_id; }; void main() { boost::scoped_ptr<CTest> pTest(new CTest); pTest->DoSomething(); } 其运行结果为: id: 0 - Doing something id: 0 - Destructor is being called (以下的几个例子所用的CTest 类的定义完全相同,为节省篇幅,不再列出——作者) 显然,尽管我们自己没有调用delete,pTest 仍然为我们正确地删除了它所指向的对象。看起来scoped_ptr 的用途和auto_ptr 十分类似,但实际上,scoped_ptr 类型的指针的所有权不可转让,这一点是和auto_ptr 相当不同的。 ?? scoped_array:该模板类与scoped_ptr 类似,但意在用于数组而不是单个对象。std::vector 可用于 替换scoped_array,并且远为灵活,但其效率要低一点。在不使用动态分配时,boost::array 也可用于替换 scoped_array。 下面是一个使用scoped_array 的实例: void main() { boost::scoped_array<CTest> pTest(new CTest[2]); pTest[0].SetId(0); pTest[1].SetId(1); pTest[0].DoSomething(); pTest[1].DoSomething(); std::cout << '\n'; } 其运行结果为: id: 0 - Doing something id: 1 - Doing something id: 1 - Destructor is being called id: 0 - Destructor is being called scoped_array 将负责使用delete [],而不是delete 来删除它所指向的对象。 ?? shared_ptr:意在用于对被指向对象的所有权进行共享。与scoped_ptr 一样,被指向对象也保证会被删 除,但不同的是,这将发生在最后一个指向它的shared_ptr 被销毁时,或是调用reset 方法时。shared_ptr 符合C++标准库的“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求,所以可被用于标 准的库容器中。另外它还提供了比较操作符,所以可与标准库的关联容器一起工作。shared_ptr 不能用于存储指 向动态分配的数组的指针,这样的情况应该使用shared_array。该模板的实现采用了引用计数技术,所以无法 正确处理循环引用的情况。可以使用weak_ptr 来“打破循环”。shared_ptr 还可在多线程环境中使用。 下面的例子演示怎样将shared_ptr 用于std::vector 中: typedef boost::shared_ptr<CTest> TestPtr; void PT(const TestPtr &t) { std::cout << "id: " << t->GetId() << "\t\t" << "use count: " << t.use_count() << '\n'; } void main() { std::vector<TestPtr> TestVector; TestPtr pTest0(new CTest(0)); TestVector.push_back(pTest0); TestPtr pTest1(new CTest(1)); TestVector.push_back(pTest1); TestPtr pTest2(new CTest(2)); TestVector.push_back(pTest2); std::for_each(TestVector.begin(), TestVector.end(), PT); std::cout << '\n'; pTest0.reset(); pTest1.reset(); pTest2.reset(); std::for_each(TestVector.begin(), TestVector.end(), PT); std::cout << '\n'; TestVector.clear(); std::cout << '\n'; std::cout << "exiting...\n"; } 其运行结果为: id: 0 use count: 2 id: 1 use count: 2 id: 2 use count: 2 id: 0 use count: 1 id: 1 use count: 1 id: 2 use count: 1 id: 0 - Destructor is being called id: 1 - Destructor is being called id: 2 - Destructor is being called exiting... 运行结果中的“use count”是通过shared_ptr 的use_count()方法获得的“使用计数”,也就是,对所存储 指针进行共享的shared_ptr 对象的数目。我们可以看到,在通过new 分配了3 个CTest 对象,并将相应的sh ared_ptr 对象放入TestVector 后,三个使用计数都为2;而在我们使用reset()方法复位pTest0、pTest 1 和pTest2 后,TestVector 中的各个shared_ptr 对象的使用计数变成了1。这时,我们调用TestVector 的clear()方法清除它所包含的shared_ptr 对象;因为已经没有shared_ptr 对象再指向我们先前分配的3 个CTest 对象,这3 个对象也随之被删除,并导致相应的析构器被调用。 ?? shared_array:该模板类与shared_ptr 类似,但意在用于数组而不是单个对象。指向std::vector 的 shared_ptr 可用于替换scoped_array,并且远为灵活,但其效率也要低一点。 下面是使用实例: void main() { boost::shared_array<CTest> pTest1(new CTest[2]); pTest1[0].SetId(0); pTest1[1].SetId(1); std::cout << "use count: " << pTest1.use_count() << "\n\n"; boost::shared_array<CTest> pTest2(pTest1); std::cout << "use count: " << pTest1.use_count() << "\n\n"; pTest1.reset(); pTest2[0].DoSomething(); pTest2[1].DoSomething(); std::cout << '\n'; std::cout << "use count: " << pTest1.use_count() << "\n\n"; } 其运行结果为: use count: 1 use count: 2 id: 0 - Doing something id: 1 - Doing something use count: 1 id: 1 - Destructor is being called id: 0 - Destructor is being called 如此例所示,我们通过new 所分配的数组只有在指向它的pTest1 和pTest2 都被销毁或复位后才被删除。 ?? weak_ptr:该模板类存储“已由shared_ptr 管理的对象”的“弱引用”。要访问weak_ptr 所指向的对 象,可以使用shared_ptr 构造器或make_shared 函数来将weak_ptr 转换为shared_ptr。指向被管理对象 的最后一个shared_ptr 被销毁时将删除该对象,即使仍有weak_ptr 指向它也是如此。与原始指针不同的是, 届时最后一个shared_ptr 会检查是否有weak_ptr 指向该对象,如果有的话就将这些weak_ptr 置为空。这样 就不会发生使用原始指针时可能出现的“悬吊指针”(dangling pointer)情况,从而获得更高的安全水平。 weak_ptr 符合C++标准库的“可复制构造”(CopyConstructible)和“可赋值”(Assignable)要求, 所以可被用于标准的库容器中。另外它还提供了比较操作符,所以可与标准库的关联容器一起工作。 void main() { boost::shared_ptr<CTest> pTest(new CTest); boost::weak_ptr<CTest> pTest2(pTest); if(boost::shared_ptr<CTest> pTest3 = boost::make_shared(pTest2)) pTest3->DoSomething(); pTest.reset(); assert(pTest2.get() == NULL); } 其运行结果为: id: 0 - Doing something id: 0 - Destructor is being called main 函数最后的断言确认了pTest2 所存储的指针的确已被置为NULL。 显然,Boost 的智能指针方案会让我们产生这样的疑问:如果我们还需要其他类型的智能指针(比如支持CO M 的智能指针),是否意味着我们必须在C++中再增加智能指针类型,或是采用非标准的实现呢?在泛型技术已得 到极大发展的今天,Boost | |||||