zz: C++ 智能指針詳解

C++ 智能指針詳解程序員

 

1、簡介編程

因爲 C++ 語言沒有自動內存回收機制,程序員每次 new 出來的內存都要手動 delete。程序員忘記 delete,流程太複雜,最終致使沒有 delete,異常致使程序過早退出,沒有執行 delete 的狀況並不罕見。數組

用智能指針即可以有效緩解這類問題,本文主要講解參見的智能指針的用法。包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr你可能會想,如此多的智能指針就爲了解決new、delete匹配問題,真的有必要嗎?看完這篇文章後,我想你內心天然會有答案。架構

    下面就按照順序講解如上 7 種智能指針(smart_ptr)。框架

 

2、具體使用函數

1、總括測試

對於編譯器來講,智能指針其實是一個棧對象,並不是指針類型,在棧對象生命期即將結束時,智能指針經過析構函數釋放有它管理的堆內存。全部智能指針都重載了「operator->」操做符,直接返回對象的引用,用以操做對象。訪問智能指針原來的方法則使用「.」操做符。spa

訪問智能指針包含的裸指針則能夠用 get() 函數。因爲智能指針是一個對象,因此if (my_smart_object)永遠爲真,要判斷智能指針的裸指針是否爲空,須要這樣判斷:if (my_smart_object.get())。架構設計

智能指針包含了 reset() 方法,若是不傳遞參數(或者傳遞 NULL),則智能指針會釋放當前管理的內存。若是傳遞一個對象,則智能指針會釋放當前對象,來管理新傳入的對象。設計

咱們編寫一個測試類來輔助分析:

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;

};

 

2、std::auto_ptr

std::auto_ptr 屬於 STL,固然在 namespace std 中,包含頭文件 #include<memory> 即可以使用。std::auto_ptr 可以方便的管理單個堆內存對象。

咱們從代碼開始分析:

void TestAutoPtr() {

std::auto_ptr<Simple> my_memory(new Simple(1));   // 建立對象,輸出:Simple:1

if (my_memory.get()) {                            // 判斷智能指針是否爲空

my_memory->PrintSomething();                    // 使用 operator-> 調用智能指針對象中的函數

my_memory.get()->info_extend = "Addition";      // 使用 get() 返回裸指針,而後給內部對象賦值

my_memory->PrintSomething();                    // 再次打印,代表上述賦值成功

(*my_memory).info_extend += " other";           // 使用 operator* 返回智能指針內部對象,而後用「.」調用智能指針對象中的函數

my_memory->PrintSomething();                    // 再次打印,代表上述賦值成功

  }

}                                                   // my_memory 棧對象即將結束生命期,析構堆對象Simple(1)

執行結果爲:

Simple: 1

PrintSomething:

PrintSomething: Addition

PrintSomething: Addition other

~Simple: 1

上述爲正常使用 std::auto_ptr 的代碼,一切彷佛都良好,不管如何不用咱們顯示使用該死的delete 了。

 

其實好景不長,咱們看看以下的另外一個例子:

void TestAutoPtr2() {

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

  if (my_memory.get()) {

    std::auto_ptr<Simple> my_memory2;   // 建立一個新的 my_memory2 對象

    my_memory2 = my_memory;             // 複製舊的 my_memory 給 my_memory2

    my_memory2->PrintSomething();       // 輸出信息,複製成功

    my_memory->PrintSomething();        // 崩潰

  }

}

最終如上代碼致使崩潰,如上代碼時絕對符合 C++ 編程思想的,竟然崩潰了,跟進std::auto_ptr 的源碼後,咱們看到,罪魁禍首是「my_memory2 = my_memory」,這行代碼,my_memory2 徹底奪取了 my_memory 的內存管理全部權,致使 my_memory 懸空,最後使用時致使崩潰。

因此,使用 std::auto_ptr 時,絕對不能使用「operator=」操做符。做爲一個庫,不容許用戶使用,確沒有明確拒絕[1],多少會以爲有點出乎預料。

 

看完 std::auto_ptr 好景不長的第一個例子後,讓咱們再來看一個:

void TestAutoPtr3() {

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

 

  if (my_memory.get()) {

    my_memory.release();

  }

}

執行結果爲:

Simple: 1

看到什麼異常了嗎?咱們建立出來的對象沒有被析構,沒有輸出「~Simple: 1」,致使內存泄露。當咱們不想讓 my_memory 繼續生存下去,咱們調用 release() 函數釋放內存,結果卻致使內存泄露(在內存受限系統中,若是my_memory佔用太多內存,咱們會考慮在使用完成後,馬上歸還,而不是等到 my_memory 結束生命期後才歸還)。

正確的代碼應該爲:

void TestAutoPtr3() {

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

  if (my_memory.get()) {

    Simple* temp_memory = my_memory.release();

    delete temp_memory;

  }

}

void TestAutoPtr3() {

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

  if (my_memory.get()) {

    my_memory.reset();  // 釋放 my_memory 內部管理的內存

  }

}

原來 std::auto_ptr 的 release() 函數只是讓出內存全部權,這顯然也不符合 C++ 編程思想。

總結:std::auto_ptr 可用來管理單個對象的對內存,可是,請注意以下幾點:

(1)    儘可能不要使用「operator=」。若是使用了,請不要再使用先前對象。

(2)    記住 release() 函數不會釋放對象,僅僅歸還全部權。

(3)    std::auto_ptr 最好不要當成參數傳遞(讀者能夠自行寫代碼肯定爲何不能)。

(4)    因爲 std::auto_ptr 的「operator=」問題,有其管理的對象不能放入 std::vector 等容器中。

(5)    ……

使用一個 std::auto_ptr 的限制還真多,還不能用來管理堆內存數組,這應該是你目前在想的事情吧,我也以爲限制挺多的,哪天一個不當心,就致使問題了。

因爲 std::auto_ptr 引起了諸多問題,一些設計並非很是符合 C++ 編程思想,因此引起了下面 boost 的智能指針,boost 智能指針能夠解決如上問題。

讓咱們繼續向下看。

 

3、boost::scoped_ptr

boost::scoped_ptr 屬於 boost 庫,定義在 namespace boost 中,包含頭文件#include<boost/smart_ptr.hpp> 即可以使用。boost::scoped_ptr 跟 std::auto_ptr 同樣,能夠方便的管理單個堆內存對象,特別的是,boost::scoped_ptr 獨享全部權,避免了 std::auto_ptr惱人的幾個問題。

咱們仍是從代碼開始分析:

void TestScopedPtr() {

  boost::scoped_ptr<Simple> my_memory(new Simple(1));

  if (my_memory.get()) {

    my_memory->PrintSomething();

    my_memory.get()->info_extend = "Addition";

    my_memory->PrintSomething();

    (*my_memory).info_extend += " other";

    my_memory->PrintSomething();

   

    my_memory.release();           // 編譯 error: scoped_ptr 沒有 release 函數

    std::auto_ptr<Simple> my_memory2;

    my_memory2 = my_memory;        // 編譯 error: scoped_ptr 沒有重載 operator=,不會致使全部權轉移

  }

}

首先,咱們能夠看到,boost::scoped_ptr 也能夠像 auto_ptr 同樣正常使用。但其沒有release() 函數,不會致使先前的內存泄露問題。其次,因爲 boost::scoped_ptr 是獨享全部權的,因此明確拒絕用戶寫「my_memory2 = my_memory」之類的語句,能夠緩解 std::auto_ptr 幾個惱人的問題。

    因爲 boost::scoped_ptr 獨享全部權,當咱們真真須要複製智能指針時,需求便知足不了了,如此咱們再引入一個智能指針,專門用於處理複製,參數傳遞的狀況,這即是以下的boost::shared_ptr。

 

4、boost::shared_ptr

boost::shared_ptr 屬於 boost 庫,定義在 namespace boost 中,包含頭文件#include<boost/smart_ptr.hpp> 即可以使用。在上面咱們看到 boost::scoped_ptr 獨享全部權,不容許賦值、拷貝,boost::shared_ptr 是專門用於共享全部權的,因爲要共享全部權,其在內部使用了引用計數。boost::shared_ptr 也是用於管理單個堆內存對象的。

咱們仍是從代碼開始分析:

void TestSharedPtr(boost::shared_ptr<Simple> memory) {  // 注意:無需使用 reference (或 const reference)

  memory->PrintSomething();

  std::cout << "TestSharedPtr UseCount: " << memory.use_count() << std::endl;

}

 

void TestSharedPtr2() {

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

  if (my_memory.get()) {

    my_memory->PrintSomething();

    my_memory.get()->info_extend = "Addition";

    my_memory->PrintSomething();

    (*my_memory).info_extend += " other";

    my_memory->PrintSomething();

  }

 

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

  TestSharedPtr(my_memory);

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

 

  //my_memory.release();// 編譯 error: 一樣,shared_ptr 也沒有 release 函數

}

執行結果爲:

Simple: 1

PrintSomething:

PrintSomething: Addition

PrintSomething: Addition other

TestSharedPtr2 UseCount: 1

PrintSomething: Addition other

TestSharedPtr UseCount: 2

TestSharedPtr2 UseCount: 1

~Simple: 1

boost::shared_ptr 也能夠很方便的使用。而且沒有 release() 函數。關鍵的一點,boost::shared_ptr 內部維護了一個引用計數,由此能夠支持複製、參數傳遞等。boost::shared_ptr 提供了一個函數 use_count() ,此函數返回 boost::shared_ptr 內部的引用計數。查看執行結果,咱們能夠看到在 TestSharedPtr2 函數中,引用計數爲 1,傳遞參數後(此處進行了一次複製),在函數TestSharedPtr 內部,引用計數爲2,在 TestSharedPtr 返回後,引用計數又下降爲 1。當咱們須要使用一個共享對象的時候,boost::shared_ptr 是再好不過的了。

在此,咱們已經看完單個對象的智能指針管理,關於智能指針管理數組,咱們接下來說到。

 

5、boost::scoped_array

boost::scoped_array 屬於 boost 庫,定義在 namespace boost 中,包含頭文件#include<boost/smart_ptr.hpp> 即可以使用。

    boost::scoped_array 即是用於管理動態數組的。跟 boost::scoped_ptr 同樣,也是獨享全部權的。

咱們仍是從代碼開始分析:

void TestScopedArray() {

      boost::scoped_array<Simple> my_memory(new Simple[2]); // 使用內存數組來初始化

      if (my_memory.get()) {

        my_memory[0].PrintSomething();

        my_memory.get()[0].info_extend = "Addition";

        my_memory[0].PrintSomething();

        (*my_memory)[0].info_extend += " other";            // 編譯 error,scoped_ptr 沒有重載 operator*

        my_memory[0].release();                             // 同上,沒有 release 函數

        boost::scoped_array<Simple> my_memory2;

        my_memory2 = my_memory;                             // 編譯 error,同上,沒有重載 operator=

      }

    }

boost::scoped_array 的使用跟 boost::scoped_ptr 差很少,不支持複製,而且初始化的時候須要使用動態數組。另外,boost::scoped_array 沒有重載「operator*」,其實這並沒有大礙,通常狀況下,咱們使用 get() 函數更明確些。

    下面確定應該講 boost::shared_array 了,一個用引用計數解決複製、參數傳遞的智能指針類。

 

6、boost::shared_array

boost::shared_array 屬於 boost 庫,定義在 namespace boost 中,包含頭文件#include<boost/smart_ptr.hpp> 即可以使用。

    因爲 boost::scoped_array 獨享全部權,顯然在不少狀況下(參數傳遞、對象賦值等)不知足需求,由此咱們引入 boost::shared_array。跟 boost::shared_ptr 同樣,內部使用了引用計數。

咱們仍是從代碼開始分析:

void TestSharedArray(boost::shared_array<Simple> memory) {  // 注意:無需使用 reference (或 const reference)

  std::cout << "TestSharedArray UseCount: " << memory.use_count() << std::endl;

}

 

void TestSharedArray2() {

  boost::shared_array<Simple> my_memory(new Simple[2]);

  if (my_memory.get()) {

    my_memory[0].PrintSomething();

    my_memory.get()[0].info_extend = "Addition 00";

    my_memory[0].PrintSomething();

    my_memory[1].PrintSomething();

    my_memory.get()[1].info_extend = "Addition 11";

    my_memory[1].PrintSomething();

    //(*my_memory)[0].info_extend += " other";  // 編譯 error,scoped_ptr 沒有重載 operator*

  }

  std::cout << "TestSharedArray2 UseCount: " << my_memory.use_count() << std::endl;

  TestSharedArray(my_memory);

  std::cout << "TestSharedArray2 UseCount: " << my_memory.use_count() << std::endl;

}

執行結果爲:

Simple: 0

Simple: 0

PrintSomething:

PrintSomething: Addition 00

PrintSomething:

PrintSomething: Addition 11

TestSharedArray2 UseCount: 1

TestSharedArray UseCount: 2

TestSharedArray2 UseCount: 1

~Simple: 0

~Simple: 0

跟 boost::shared_ptr 同樣,使用了引用計數,能夠複製,經過參數來傳遞。

 

至此,咱們講過的智能指針有std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array。這幾個智能指針已經基本夠咱們使用了,90% 的使用過標準智能指針的代碼就這 5 種。可以下還有兩種智能指針,它們確定有用,但有什麼用處呢,一塊兒看看吧。

 

7、boost::weak_ptr

boost::weak_ptr 屬於 boost 庫,定義在 namespace boost 中,包含頭文件#include<boost/smart_ptr.hpp> 即可以使用。

在講 boost::weak_ptr 以前,讓咱們先回顧一下前面講解的內容。彷佛boost::scoped_ptr、boost::shared_ptr 這兩個智能指針就能夠解決全部單個對象內存的管理了,這兒還多出一個 boost::weak_ptr,是否還有某些狀況咱們沒歸入考慮呢?

回答:有。首先 boost::weak_ptr 是專門爲 boost::shared_ptr 而準備的。有時候,咱們只關心可否使用對象,並不關心內部的引用計數。boost::weak_ptr 是 boost::shared_ptr 的觀察者(Observer)對象,觀察者意味着 boost::weak_ptr 只對 boost::shared_ptr 進行引用,而不改變其引用計數,當被觀察的 boost::shared_ptr 失效後,相應的 boost::weak_ptr 也相應失效。

咱們仍是從代碼開始分析:

    void TestWeakPtr() {

      boost::weak_ptr<Simple> my_memory_weak;

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

 

      std::cout << "TestWeakPtr boost::shared_ptr UseCount: " << my_memory.use_count() << std::endl;

      my_memory_weak = my_memory;

      std::cout << "TestWeakPtr boost::shared_ptr UseCount: " << my_memory.use_count() << std::endl;

}

    執行結果爲:

Simple: 1

TestWeakPtr boost::shared_ptr UseCount: 1

TestWeakPtr boost::shared_ptr UseCount: 1

~Simple: 1

    咱們看到,儘管被賦值了,內部的引用計數並無什麼變化,固然,讀者也能夠試試傳遞參數等其餘狀況。

    如今要說的問題是,boost::weak_ptr 到底有什麼做用呢?從上面那個例子看來,彷佛沒有任何做用,其實 boost::weak_ptr 主要用在軟件架構設計中,能夠在基類(此處的基類並不是抽象基類,而是指繼承於抽象基類的虛基類)中定義一個 boost::weak_ptr,用於指向子類的boost::shared_ptr,這樣基類僅僅觀察本身的 boost::weak_ptr 是否爲空就知道子類有沒對本身賦值了,而不用影響子類 boost::shared_ptr 的引用計數,用以下降複雜度,更好的管理對象。

 

    8、boost::intrusive_ptr

boost::intrusive_ptr屬於 boost 庫,定義在 namespace boost 中,包含頭文件#include<boost/smart_ptr.hpp> 即可以使用。

講完如上 6 種智能指針後,對於通常程序來講 C++ 堆內存管理就夠用了,如今有多了一種boost::intrusive_ptr,這是一種插入式的智能指針,內部不含有引用計數,須要程序員本身加入引用計數,否則編譯不過(⊙﹏⊙b汗)。我的感受這個智能指針沒太大用處,至少我沒用過。有興趣的朋友本身研究一下源代碼哦J

 

 

3、總結

如上講了這麼多智能指針,有必要對這些智能指針作個總結:

1、在可使用 boost 庫的場合下,拒絕使用 std::auto_ptr,由於其不只不符合 C++ 編程思想,並且極容易出錯[2]。

2、在肯定對象無需共享的狀況下,使用 boost::scoped_ptr(固然動態數組使用boost::scoped_array)。

3、在對象須要共享的狀況下,使用 boost::shared_ptr(固然動態數組使用boost::shared_array)。

4、在須要訪問 boost::shared_ptr 對象,而又不想改變其引用計數的狀況下,使用boost::weak_ptr,通常經常使用於軟件框架設計中。

5、最後一點,也是要求最苛刻一點:在你的代碼中,不要出現 delete 關鍵字(或 C 語言的free 函數),由於能夠用智能指針去管理。

 

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

[1]參見《effective C++(3rd)》,條款06 。

[2]關於 boost 庫的使用,可本博客另一篇文章:《在 Windows 中編譯 boost1.42.0》。

[3]讀者應該看到了,在我全部的名字前,都加了命名空間標識符std::(或boost::),這不是我不想寫 using namespace XXX 之類的語句,在大型項目中,有可能會用到 N 個第三方庫,若是把命名空間全放出來,命名污染(Naming conflicts)問題很難避免,到時要改回來是極端麻煩的事情。固然,若是你只是寫 Demo,能夠例外。

相關文章
相關標籤/搜索