智能指针的标准之争:Boost vs. Loki
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 class scoped_ptr;
template class scoped_array;
template class shared_ptr;
template class shared_array;
template class weak_ptr;
下面将分别介绍它们各自的特性,并给出相应的使用实例:
scoped_ptr:意在用作指向自动(栈)对象的、不可复制的智能指针。该模板类存储的是指向动态分配的对象(通过new 分配)的指针。被指向的对象保证会被删除,或是在scoped_ptr 析构时,或是通过显式地调用reset 方法。注意该模板没有“共享所有权”或是“所有权转让”语义。同时,它也是不可复制的(noncopyable)。正因为如此,在用于不应被复制的指针时,它比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 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 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 TestPtr;
void PT(const TestPtr &t)
{
std::cout << "id: " << t->GetId() << "\t\t" << "use count: " << t.use_count() << '\n';
}
void main()
{
std::vector 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