本來沒有打算自己翻譯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
這可能很有趣,但當然是不正常的,你作為一個STL的使用者,要你小心使用的原因是,auto_ptr會導致一些莫名其妙的結果。舉例來說,詳細看看下面看似“清白無辜”的代碼,這些代碼先產生一個包含atuo_ptr<Widget>的vector,然後通過比較指向Widgets的指針來對vector進行排序:
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
//not compile!
sort(widgets.begin(), widgets.end(), // sort the vector
widgetAPCompare);
不管這些代碼看起來有多麼合理,這個運行結果肯定是不合理的。最簡單的,排序的時候,widgets中會有一個或者多個auto_ptrs的值被改為NULL。vector本身的排序操作,卻改變了容器本身的值。把這其中的原理弄明白還是比較值得的,我們來推斷一下:
它可能是因為sort算法的實現(一個公用的方法,並被證明過的),在快速排序算法過程中不得不使用一些臨時變量。我們並不關心快速排序算法的具體實現,但其基本概念是,排序一個容器中的元素,必定會有某個元素用來作為“pivot element”,然會遞歸比較一個元素比這個pivot element大於小於或等於進行排序。在排序內部,實現方法可能像這樣:
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!