条款8:不要把auto_ptr放入容器
作者: winter
本来没有打算自己翻译Effective STL的,怕影响大家情绪:),只是发现有些条款在网络上找不到,只好自己翻译了。--Winter
坦白的讲,这个条款不应加入本书,因为包含atuo_ptr的容器(COAPs)本来在STL中就是禁止的。就算你这么用了,编译器也不会编译你的代码。而标准委员会也没有解释为什么不能这样。对于COAPs我应该什么都不用说的,因为你的编译器应该替你做好了一切工作,所有这种类似的代码都不应该被编译。
可惜的是,许多程序员使用的STL版本并不拒绝COAPS,更要命的是,许多程序员发现这个曾经非常简单直接用于减少内存泄漏的自动指针经常伴随指针容器。因此虽然STL可能并不支持,许多程序员还是去试图使用COAPs。
我来讲解COAPs到底有什么缺点,以至于标准委员会都明确禁止它。好了,马上开始。我讲一个不足点,不用auto_ptr的相关知识,甚至不用任何STL容器的知识你都能明白:COAPs不灵活(portable)。他们到底能怎样?C++标准中就禁止它了,好一点的STL平台将都不容许它的存在,这些都是有原因的,我会慢慢告诉你。当前的STL平台没有禁止COAPs,你会发现存在COAPs时,代码比以前更加不灵活了。只要你很看中灵活性(当然应该如此),你就应该拒绝使用COAPs。
但是可能你并不关心灵活性,如果真是这样,请允许我友善的提醒你,就是拷贝atuo_ptr的独特的(也可以说成是奇怪的)定义。
当你拷贝一个auto_ptr,被auto_ptr所指的对象的所有权已经转移到了新的auto_ptr中去了,原有的auto_ptr被设置为NULL。你理解的没有错:拷贝一个auto_ptr,将会改变auto_ptr本身的值。
auto_ptr<Widget> pw1 (new Widget); // pwl1points to a Widget
auto_ptr<Widget> pw2(pw1); // pw2 points to pw1's Widget;
// pw1 is set to NULL. (Ownership
// of the Widget is transferred
//from pw1 to pw2.)
pw1 = pw2; // pw1 now points to the Widget
// again; pw2 is set to NULL
bool widgetAPCompare(const auto_ptr<Widget>& lhs,
const auto_ptr<Widget>& rhs) {
return *lhs < *rhs; //for this example, assume that
} // operator< exists for Widgets
vector<auto_ptr<Widget> > widgets; // create a vector and then fill it
//with auto_ptrs to Widgets;
// remember that this should
//
不管这些代码看起来有多么合理,这个运行结果肯定是不合理的。最简单的,排序的时候,widgets中会有一个或者多个auto_ptrs的值被改为NULL。vector本身的排序操作,却改变了容器本身的值。把这其中的原理弄明白还是比较值得的,我们来推断一下:
它可能是因为sort算法的实现(一个公用的方法,并被证明过的),在快速排序算法过程中不得不使用一些临时变量。我们并不关心快速排序算法的具体实现,但其基本概念是,排序一个容器中的元素,必定会有某个元素用来作为“pivot element”,然会递归比较一个元素比这个pivot element大于小于或等于进行排序。在排序内部,实现方法可能像这样:
not compile!sort(widgets.begin(), widgets.end(), // sort the vector
widgetAPCompare);
template<class RandomAccesslterator, // this declaration for
class Compare> // sort is copied straight
void sort( RandomAccesslterator first, // out of the Standard
RandomAccesslterator last,
Compare comp)
{
// this typedef is described below
typedef typename iterator_traits<RandomAccesslterator>::value_type
ElementType;
RandomAccesslterator i;
… // make i point to the pivot element
ElementType pivotValue(*i); //copy the pivot element into a
// local temporary variable; see
//discussion below
… //do the rest of the sorting work
}
除非你经常阅读STL源代码,否则你可能对这样的代码感到恐惧,其实,这些并不真的那么坏。里面唯一容易迷惑的是 iterator_traits<RandomAccesslterator>::value_type的引用,那却正是STL的经典方法,当排序的时候,通过它来识别迭代器传过来的对象。如果我们想使用iterator_traits<RandomAccesslterator>::value_type,在它之前必须写typename,因为这是一个依赖于模板参数的类型名称,在这里,是RandomAccesslterator。如果想了解更多关于typename的信息,返回第七页) 在上面代码中,那个引起麻烦的语句就是这行:
ElementType pivotValue(*i);
因为它从被排序的范围中拷贝了一个元素到本地的临时对象中,在我们的讨论中,这个元素是一个自动指针auto_ptr<Widget>,所以这个动作让被拷贝的那个auto_ptr的值变成了NULL,也就是在vector中的元素的值变成了NULL,而且当结束pivotValue的定义域时,它又自动删除了它指向的Widget。如果此时让sort函数直接返回,vector容器的值已经发生了变化,至少又一个Widget的值被释放了。由于快速排序是一个递归算法,每次递归都会选择pivot element,因此将会又好几个vector的元素被设置为NULL,同时会有好几个Widget对象被释放。
这是一个非常肮脏的陷阱,这也是为什么标准委员会如此努力的工作以避免你不会掉入这个陷阱中。为了尊重他们的工作,也为了你自己的利益,绝不要把auto_ptrs放入你的容器,甚至当你的STL平台允许你这么做,你也不要这么做。
如果你的目标是用一个包含smart pointer(智能指针)的容器,你还是幸运的。包含smart pointers的容易都可以很好的工作。条款50,描述了在哪儿你能找到和STL容器结合十分完美的smart pointer。只是你不应该把auto_ptr放入容器,而不是smart pointer!
这可能很有趣,但当然是不正常的,你作为一个STL的使用者,要你小心使用的原因是,auto_ptr会导致一些莫名其妙的结果。举例来说,详细看看下面看似“清白无辜”的代码,这些代码先产生一个包含atuo_ptr<Widget>的vector,然后通过比较指向Widgets的指针来对vector进行排序: