「智能指針」的成長

  智能指針是什麼,他的真面目就是一個類生成的對象,這個類中包含了基本的重載->、*等一些指針形態的用法,最主要的是這個類裏面有一個指針參數:全部智能指針類中都有一個explicit構造函數,以指針做爲參數。好比auto_ptr的類模板原型爲:程序員

template<class T>數組

class auto_ptr{函數

  explicit auto_ptr(T* p = 0);編碼

};spa

 

代碼中構造函數用了explicit關鍵字主要是防止隱式轉換,舉個例子:設計

auto_ptr<double> pd;指針

double *p_reg = new double;對象

pd = p_reg;       //errorblog

pd = shared_ptr<double>(p_reg);    //error生命週期

shared_ptr<double> pshared = p_reg;     //error

pd = auto_ptr<double>(p_reg);

shared_ptr<double> pshared(p_reg);

 

上面解釋了一下智能指針的本質是什麼,那它有什麼做用呢,看代碼看不出來,但仔細想,做爲一個類對象,建立時須要構造,那待生命週期結束,也是須要自動析構的,因此就帶出了他的功能,就是爲了保證其指向的New出來的對象,能夠在生命週期結束時自動Delete,做爲程序員咱們都知道,Delete這個東西咱們常常忘寫不說,也在不少return的地方忽略了寫Delete,這就致使了內存泄漏,很致命。

因此C++就提供了一種智能指針的模式去防止這種現象,現有最經常使用的智能指針,分別有 std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_arrayboost::weak_ptr這6個智能指針都有各自的使用範疇,話很少說,研究下就知道。

 

⑴ std::auto_ptr

  首先auto_ptr這個智能指針,屬於STL,同屬於STL的還有unique_ptr、shared_ptr等,感興趣的能夠去查查,但本文就不進行介紹了。先來看一段代碼

 

class Simple {

 public:

  Simple(int param = 0) {

     number = param;

     std::cout << "Simple: " << number << std::endl; 

  }

  ~Simple() {

     std::cout << "~Simple: " << number << std::endl;

  }

  void PrintSomething() {

    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;

  }

  std::string info_extend;

  int number;

};

void fun()

{

  std::auto_ptr<Simple> ptr (new Simple(1));

  if(ptr.get())

  {

    ptr->PrintSomething();

    ptr.get()->info_extend = "Hello";

    (*prt)->info_extend = "World";

  }

}

上一段代碼運行的結果爲:

  Simple:1

  PrintSometing:

  ~Simple:1

這是最標準的一種智能指針的使用方法,對於auto_ptr這種智能指針也是徹底能駕馭的,但假如咱們在代碼中加入另外幾句

std::auto_ptr<Simple> ptr2;

ptr2 = ptr;

pt2->PrintSomething();

pt1->PrintSomething(); // 崩潰

若是將上面的代碼加到程序中就會崩潰,爲何呢?看源碼中,重載=時,進行了

reset(_Right.release());

繼續向裏執行release的代碼

 

能夠看到,這裏是將本來的指針控制權交給了別人,自身已經被置爲NULL了。因此咱們進行調用,不崩潰等什麼呢?

而後加另外一端代碼:

if (ptr.get()) {

  ptr.release();

}

咱們能夠直接調用release函數,這就很尷尬了,直接返回值爲當前指向的控制權,但沒有指針接收,直接丟棄了,這就更尷尬了,致使只進行了構造,沒有析構。。因此真要使用release函數的話,必須這樣

Simple* ptr2 = ptr.release();

delete ptr2;

這噁心的用法總給人一種畫蛇添足的作法,因此我release函數的直接使用是沒有任何意義的。對於這種手動的釋放內存,直接調用

ptr.reset();

 

因此咱們對auto_ptr進行一個總結:

  1,儘可能不要使用"="對智能指針進行賦值,若是使用,那先前對象就時一個空指針的。

  2,release函數單獨使用沒有任何意義,不會釋放對象,還有可能致使對象丟失。他的做用僅僅是返回全部權。

  3,不要把智能指針對象當作參數傳遞進入函數,由於也會調用release,致使調用完函數,回來對象已經被析構。

  上述3個僅僅是我的發現的,對於auto_ptr自己就有不少不合理的地方,雖然還有人使用,但具體項目中用的已經很少了,因此就在auto_ptr的基礎上,增長了各類其餘的智能指針,用以修復這個使用不合理的地方。

 

⑵ boost::scoped_ptr

  scoped_ptr屬於boost庫,他與auto_ptr相比,也是避免了上述幾個問題,主要的緣由就是scoped的設計爲獨享全部權。主要的體現就是調用release函數和‘=’的時候是會報錯的。雖然表面上的確是避免了auto_ptr中出現的問題,但種感受是一種逃避的方法,那就有了下面的另外一種智能指針。

 

⑶ boost::shared_ptr

  scoped_ptr屬於boost庫,與上述的scoped_ptr相比,它添加了共享全部權的功能,也就是可使用‘=’方法,關於作法,原理就是在類中使用了一個引用計數,當進行賦值的時候對該數字加1,寫一段代碼看看:

void TestSharedPtr(boost::shared_ptr<Simple> ptr)

{

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

}

boost::shared_ptr<Simple> ptr(new Simple(1));

std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

{

  boost::shared_ptr<Simple> ptr2 = ptr;

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

  TestSharedPtr(ptr);

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

}

std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

這一段代碼運行下來的打印結果是:

TestSharedPtr2 UseCount: 1

TestSharedPtr2 UseCount: 2

TestSharedPtr2 UseCount: 3

TestSharedPtr2 UseCount: 2

TestSharedPtr2 UseCount: 1

大概解釋一下,在開始只有一個ptr時,打印出來的引用計數爲1,當另外使用一個ptr2時,再次打印出來爲2,意思就是當前共有兩個智能指針同時使用當前對象。進入函數後,又進行了一次拷貝構造到臨時變量,因此打印值又變成3,後續退出函數和退出ptr2的生命週期,引用計數的值各剪了1,當代碼執行完畢後,引用u計數值歸0,調用析構函數時使用Delete釋放空間。

 

⑷ boost::scoped_array

以數組的方式管理多個對象,但屬性與scoped_ptr徹底一致,獨享全部權,而其使用方法也與數組沒有太大的區別:

boost::scoped_array<Simple> ptr_array(new Simple[2]);

ptr_array[0].PrintSomething();

 

⑸ boost::shared_array

以數組的方式管理多個對象,屬性與shared_ptr王權一致,共享全部權,內部使用引用計數

 

⑹ boost::weak_ptr

weak_ptr是一中比較特殊的智能指針,其應用的場景比較單一,主要是對shared_ptr進行觀察的一種對象,也就是說用weak_ptr類型的對象對shared_ptr類型的對象進行引用,是能夠正常訪問,但不會改變後者的引用計數值,當後者被銷燬後,前者也就不能使用了。如今說說它的應用場景,若是子類中有一個shared_ptr類型的對象,那基類中就能夠定義一個weak_ptr類型的對象,經過訪問基類的weak_ptr對象是否爲空,就能夠判斷子類是否對本身進行賦值。

網上看了一些博客,發現weak_ptr還有一個很經常使用的場景,若是shared_ptr產生循環引用問題,就須要使用弱指針week與強引用指針相結合使用,什麼是循環引用問題呢?看段代碼:

{

  shared_ptr<int> tr1(new Node());

  shared_ptr<int> tr2(new Node());

  tr1 -> next = tr2;

  tr2 -> pre = tr1;

}

這段代碼若是運行到}處,致使tr1,tr2生命週期結束,須要本身調用析構函數時,就會發生崩潰,緣由是什麼呢?主要就是在調用析構,發現當前引用計數爲1,那就先去讓別人析構,但tr2的引用計數一樣爲2,兩個互相讓對方先析構,就陷入了一個的僵局,致使最後均不釋放,內存泄漏。對於這種狀況解決的辦法能夠有好幾種:

⑴ 作一個監控,當最後發現兩個智能指針陷入循環階段,手動打破僵局(定製一個定時器就能夠解決)

⑵ 模仿子類或父類的析構方式,若是陷入僵局,則先析構後構造的對象,好比先析構tr2,再析構tr1

⑶ 最簡單也是最經常使用的,使用弱引用型智能指針week_ptr,不會改變引用計數的值,當須要訪問的對象已經被析構,能夠經過lock()函數返回一個空的shared_ptr,因此就能夠自動處理了。因此只須要將上面的代碼tr2類型改成weel_ptr便可解決循環引用的問題。

 

本文有部分函數名和編碼方式是採用網上其餘博主的風格(由於以爲這種講解方法特別清晰,先簡單介紹,而後上代碼直接理解),因此有些抄襲的嫌疑,但所有均本人理解,並從新編碼以後寫上去的,不少地方也是本人經過理解源碼,寫出來的部分東西,若是有誤,請及時溝通修正。

相關文章
相關標籤/搜索