爲何auto_ptr智能指針不能做爲STL標準容器的元素


上個星期的博客shared_ptr源碼剖析裏其實遺漏了一個問題:爲何auto_ptr不能夠做爲STL標準容器的元素,而shared_ptr能夠?  我在網上看了好多篇講shared_ptr的文章裏講到了這個問題,不過大多文章只是簡單兩筆帶過。我研究了一下這個問題,發現仍是有挺多有價值的內容,因此把這個問題單獨成一篇博客和你們分享。 c++

先從表象上看看這個問題,假若有這樣的一段代碼,是否可以運行? express


int costa_foo()
{
    vector< auto_ptr<int> > v(10);
    int i=0;
    for(;i<10;i++)
    {   
        v[i]=auto_ptr<int>(new int(i));
    }   
}
答案是否認的,甚至這段代碼是沒法編譯經過的。g++編譯器會報下面這個錯誤:



In file included from /usr/include/c++/4.4/memory:51,
                 from foo.cpp:x:
/usr/include/c++/4.4/bits/stl_construct.h: In function ‘void std::_Construct(_T1*, const _T2&) [with _T1 = std::auto_ptr<int>, _T2 = std::auto_ptr<int>]’:
/usr/include/c++/4.4/bits/stl_uninitialized.h:187:   instantiated from ‘static void std::__uninitialized_fill_n<<anonymous> >::uninitialized_fill_n(_ForwardIterator, _Size, const _Tp&) [with _ForwardIterator = std::auto_ptr<int>*, _Size = unsigned int, _Tp = std::auto_ptr<int>, bool <anonymous> = false]’
/usr/include/c++/4.4/bits/stl_uninitialized.h:223:   instantiated from ‘void std::uninitialized_fill_n(_ForwardIterator, _Size, const _Tp&) [with _ForwardIterator = std::auto_ptr<int>*, _Size = unsigned int, _Tp = std::auto_ptr<int>]’
/usr/include/c++/4.4/bits/stl_uninitialized.h:318:   instantiated from ‘void std::__uninitialized_fill_n_a(_ForwardIterator, _Size, const _Tp&, std::allocator<_Tp2>&) [with _ForwardIterator = std::auto_ptr<int>*, _Size = unsigned int, _Tp = std::auto_ptr<int>, _Tp2 = std::auto_ptr<int>]’
/usr/include/c++/4.4/bits/stl_vector.h:1035:   instantiated from ‘void std::vector<_Tp, _Alloc>::_M_fill_initialize(size_t, const _Tp&) [with _Tp = std::auto_ptr<int>, _Alloc = std::allocator<std::auto_ptr<int> >]’
/usr/include/c++/4.4/bits/stl_vector.h:230:   instantiated from ‘std::vector<_Tp, _Alloc>::vector(size_t, const _Tp&, const _Alloc&) [with _Tp = std::auto_ptr<int>, _Alloc = std::allocator<std::auto_ptr<int> >]’
foo.cpp:22:   instantiated from here
/usr/include/c++/4.4/bits/stl_construct.h:74: error: passing ‘const std::auto_ptr<int>’ as ‘this’ argument of ‘std::auto_ptr<_Tp>::operator std::auto_ptr_ref<_Tp1>() [with _Tp1 = int, _Tp = int]’ discards qualifiers

錯誤出在這一行: 函數

vector< auto_ptr<int> > v(10);



這個錯誤是什麼含義呢,咱們看stl_construct.h的74行所在的函數: ui

64   /**
 65    * Constructs an object in existing memory by invoking an allocated
 66    * object's constructor with an initializer.
 67    */
 68   template<typename _T1, typename _T2>
 69     inline void
 70     _Construct(_T1* __p, const _T2& __value)
 71     {
 72       // _GLIBCXX_RESOLVE_LIB_DEFECTS
 73       // 402. wrong new expression in [some_]allocator::construct
 74       ::new(static_cast<void*>(__p)) _T1(__value);
 75     }
我來直接說這個函數的做用:把第二個參數__T2& value拷貝構造一份,而後複製到T1這個指針所指向的位置。它是如何作到的呢?


看第第74行, 這裏使用new的方法和咱們日常所見到的彷佛略有不一樣。 這是一個placement new。 placement new的語法是: this

new(p) T(value)

placement new並不會去堆上申請一塊內存,而是直接使用指針p指向的內存,將value對象拷貝一份放到p指向的內存上去。 spa


看到這裏我就知道爲何編譯器在編譯本文開頭的那段代碼時會報這段錯誤」 /usr/include/c++/4.4/bits/stl_construct.h:74: error: passing ‘const std::auto_ptr<int>’ as ‘this’ argument of ‘std::auto_ptr<_Tp>::operator std::auto_ptr_ref<_Tp1>() [with _Tp1 = int, _Tp = int]’ discards qualifiers" 了。經過查看auto_ptr的源代碼(位置在 c++頭文件目錄的 backward/auto_ptr.h)能夠發現, auto_ptr並無一個參數爲const auto_ptr& 的拷貝構造函數。換而言之, auto_ptr進行拷貝構造的時候,必須要修改做爲參數傳進來的那個auto_ptr。 .net

auto_ptr的拷貝構造函數是這樣寫的: 設計

auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

能夠看出來, 在拷貝構造一個auto_tr的時候, 必須要把參數的那個auto_ptr給release掉,而後在把參數的_M_ptr指針賦值給本身的_M_ptr指針。補充說明一下, _M_ptr是auto_ptr的一個成員,指向auto_ptr管理的那塊內存,也就是在auto_ptr生命週期以外被釋放掉的那塊內存。 指針

當看到這裏的時候,整個問題已經能解釋通了。STL容器在分配內存的時候,必需要可以拷貝構造容器的元素。並且拷貝構造的時候,不能修改原來元素的值。而auto_ptr在拷貝構造的時候,必定會修改元素的值。因此STL元素不能使用auto_ptr。 code

不過,還有一個很重要的問題沒有解釋。那就是爲何在設計auto_ptr的時候,拷貝構造要修改參數的值呢?

其實這問題很簡單,不看代碼也能夠解釋清楚。auto_ptr內部有一個指針成員_M_ptr,指向它所管理的那塊內存。而拷貝構造的時候,首先把參數的_M_ptr的值賦值給本身的_M_ptr,而後把參數的_M_ptr指針設成NULL,。若是不這樣設計,而是直接把參數的_M_ptr指針賦值給本身的, 那麼兩個auto_ptr的_M_ptr指向同一塊內存,在析構auto_ptr的時候就會出問題: 假如兩個auto_ptr的_M_ptr指針指向了同一塊內存,那麼第一個析構的那個auto_ptr指針就把_M_ptr指向的內存釋放掉了,形成後一個auto_ptr在析構時釋要放一塊已經被釋放掉的內存,這明顯不科學,會產生程序的段錯誤而crash掉。

而shared_ptr則不存在這個問題, 在拷貝構造和賦值操做的時候,只會引發公用的引用計數的+1,不存在拷貝構造和賦值操做的參數不能是const的問題。

總結:

1 auto_ptr不能做爲STL標準容器的元素。

2 auto_ptr在拷貝複製和賦值操做時,都會改變參數。這是由於兩個auto_ptr不能管理同一塊內存。

----------------------------------------------------------------------------------

2013年8月16日


看來真是有學而時習之的必要。 10分鐘以前我居然在搜這個問題,而全然忘了之前本身研究過。

不過看到stackoverflow上的回答以爲比我寫的簡單多了, 抄過來:

原文連接

http://stackoverflow.com/questions/111478/why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers

The C++ Standard says that an STL element must be "copy-constructible" and "assignable." In other words, an element must be able to be assigned or copied and the two elements are logically independent.std::auto_ptrdoes not fulfill this requirement.

Take for example this code:

class X
{
};

std::vector<std::auto_ptr<X> > vecX;
vecX.push_back(new X);

std::auto_ptr<X> pX = vecX[0];  // vecX[0] is assigned NULL

To overcome this limitation, you should use the std::unique_ptr, std::shared_ptr or std::weak_ptr smart pointers or the boost equivalents if you don't have C++11. Here is the boost library documentation for these smart pointers.


而後又去翻了一下auto_ptr的源碼,  看到operator =的代碼這樣寫的:

223       operator=(auto_ptr& __a) throw()
224       {
225     reset(__a.release());
226     return *this;
227       }


多個auto_ptr不能管理同一片內存, 執行=的時候,就把原來的auto_ptr給幹掉。其實從邏輯上來說,若是多個auto_ptr管理同一塊內存確定有問題。第一個auto_ptr析構的時候就應該把內存釋放掉了, 第二個auto_ptr再析構的時候,就要去釋放已經被釋放的內存了, 程序確定掛掉。

相關文章
相關標籤/搜索