C++ 智能指針 C++11

1、緣起程序員

C++ 語言沒有自動內存回收機制,每次 new 出來的內存都要手動 delete。編程

程序員忘記 delete,流程太複雜,最終致使沒有 delete;數組

異常致使程序過早退出,沒有執行 delete 的狀況並不罕見。架構

用智能指針即可以有效緩解這類問題,本文主要講解參見的智能指針的用法。框架

包括:std::auto_ptrstd::shared_ptrstd::weak_ptr、 std::unique_ptr你可能會想,如此多的智能指針就爲了解決new、delete匹配問題,真的有必要嗎?看文章。函數

  

2、具體使用測試

1、總括spa

對於編譯器來講,智能指針 其實是一個 棧對象,並不是指針類型,在棧對象生命期即將結束時,智能指針 經過 析構函數(智能指針的) 釋放由它管理的堆內存架構設計

全部智能指針都重載了 「operator->」 操做符,直接返回對象的引用,用以操做對象。訪問 智能指針 原來的方法則使用 「.」操做符設計

訪問智能指針包含的 裸指針 則能夠用 get() 函數。

因爲智能指針是一個對象,因此 if (my_smart_object) 永遠爲真,要判斷 智能指針的裸指針是否爲空,須要這樣判斷:if (my_smart_object.get())。

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

懸空指針(Dangling Pointer)指的是:一個指針的指向對象已被刪除,那麼就成了懸空指針

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

 1 class Simple {
 2  public:
 3   Simple(int param = 0) {
 4     number = param;
 5     std::cout << "Simple: " << number << std::endl;  
 6   }
 7 
 8   ~Simple() {
 9     std::cout << "~Simple: " << number << std::endl;
10   }
11 
12   void PrintSomething() {
13     std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;
14   }
15   std::string info_extend;
16   int number;
17 };

2、std::auto_ptr

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

咱們從代碼開始分析:

 1 class Simple {
 2  public:
 3   Simple(int param = 0) {
 4     number = param;
 5     std::cout << "Simple: " << number << std::endl;  
 6   }
 7 
 8   ~Simple() {
 9     std::cout << "~Simple: " << number << std::endl;
10   }
11 
12   void PrintSomething() {
13     std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;
14   }
15   std::string info_extend;
16   int number;
17 };

 

 1 void TestAutoPtr() {
 2 
 3 std::auto_ptr<Simple> my_memory(new Simple(1));   // 建立對象,輸出:Simple:1
 4 if (my_memory.get()) {  // 判斷智能指針是否爲空
 5     my_memory->PrintSomething();     // 使用 operator-> 調用智能指針對象中的函數
 6 my_memory.get()->info_extend = "Addition";      // 使用 get() 返回裸指針,而後給內部對象賦值
 7 my_memory->PrintSomething();     // 再次打印,代表上述賦值成功
 8 (*my_memory).info_extend += " other";       // 使用 operator* 返回智能指針內部對象,而後用「.」調用智能指針對象中的函數
 9 my_memory->PrintSomething();       // 再次打印,代表上述賦值成功
10   }
11 }       // my_memory 棧對象即將結束生命期,析構堆對象 Simple(1

執行結果爲:

Simple: 1

PrintSomething:

PrintSomething: Addition

PrintSomething: Addition other

~Simple: 1

上述爲正常使用 std::auto_ptr 的代碼,一切彷佛都良好,不用顯式調用 delete函數,智能指針 經過 析構函數 釋放由它管理的堆內存

 

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

 1 void TestAutoPtr2() {
 2 
 3   std::auto_ptr<Simple> my_memory(new Simple(1));
 4   if (my_memory.get()) {
 5     std::auto_ptr<Simple> my_memory2;   // 建立一個新的 my_memory2 對象
 6     my_memory2 = my_memory;        // 複製舊的 my_memory 給 my_memory2,my_memory2 徹底奪取了 my_memory 的內存管理全部權,
                       // 致使 my_memory 懸空,最後使用時致使崩潰。
7 my_memory2->PrintSomething(); // 輸出信息,複製成功 8 my_memory->PrintSomething(); // 崩潰 9 10 } 11 }

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

因此,使用 std::auto_ptr 時,絕對不能使用「operator=」操做符 (由於內存管理全部權會轉移

 

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

1 void TestAutoPtr3() {
2   std::auto_ptr<Simple> my_memory(new Simple(1));
3 
4       if (my_memory.get()) {
5         my_memory.release();
6       }
7 
8 }

執行結果爲:

Simple: 1

看到什麼異常了嗎?咱們建立出來的對象沒有被析構,沒有輸出「~Simple: 1」,致使內存泄漏

正確的代碼應該爲:

 1 void TestAutoPtr3() {
 2 
 3   std::auto_ptr<Simple> my_memory(new Simple(1));
 4   if (my_memory.get()) {
 5     Simple* temp_memory = my_memory.release();
 6     delete temp_memory;
 7   }
 8 }
 9 10 void TestAutoPtr3() {
11 
12   std::auto_ptr<Simple> my_memory(new Simple(1));
13   if (my_memory.get()) {
14     my_memory.reset();  // 釋放 my_memory 內部管理的內存
15   }
16 }

原來 std::auto_ptr 的 release() 函數只是讓出內存全部權

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

(1)    儘可能不要使用「operator=」。若是使用了,請不要再使用先前對象,由於內存管理全部權轉移給了新對象

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

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

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

使用一個 std::auto_ptr 的限制還真多,還不能用來管理堆內存數組。

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

讓咱們繼續向下看。

3、std::shared_ptr

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

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

 1 class Simple {
 2  public:
 3   Simple(int param = 0) {
 4     number = param;
 5     std::cout << "Simple: " << number << std::endl;  
 6   }
 7 
 8   ~Simple() {
 9     std::cout << "~Simple: " << number << std::endl;
10   }
11 
12   void PrintSomething() {
13     std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;
14   }
15   std::string info_extend;
16   int number;
17 };

 

 1 void TestSharedPtr(std::shared_ptr<Simple> memory) {  // 注意:無需使用 reference (或 const reference)
 2   memory->PrintSomething();
 3   std::cout << "TestSharedPtr UseCount: " << memory.use_count() << std::endl;
 4 
 5 }
 6 
 7 void TestSharedPtr2() {
 8   std::shared_ptr<Simple> my_memory(new Simple(1));
 9   if (my_memory.get()) {
10     my_memory->PrintSomething();
11     my_memory.get()->info_extend = "Addition";
12     my_memory->PrintSomething();
13     (*my_memory).info_extend += " other";
14     my_memory->PrintSomething();
15   }
16   std::cout << "TestSharedPtr2 UseCount: " << my_memory.use_count() << std::endl;
17   TestSharedPtr(my_memory);
18   std::cout << "TestSharedPtr2 UseCount: " << my_memory.use_count() << std::endl;
19   //my_memory.release();// 編譯 error: 一樣,shared_ptr 也沒有 release 函數
20 }

執行結果爲:

Simple: 1

PrintSomething:

PrintSomething: Addition

PrintSomething: Addition other

TestSharedPtr2 UseCount: 1

PrintSomething: Addition other

TestSharedPtr UseCount: 2

TestSharedPtr2 UseCount: 1

~Simple: 1

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

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

4、std::weak_ptr

std::weak_ptr 屬於std庫,包含頭文件 #include<memory> 即可以使用。

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

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

 

1  void TestWeakPtr() {
2       std::weak_ptr<Simple> my_memory_weak;
3       std::shared_ptr<Simple> my_memory(new Simple(1));
4       std::cout << "TestWeakPtr std::shared_ptr UseCount: " << my_memory.use_count() << std::endl;
5       my_memory_weak = my_memory;
6       std::cout << "TestWeakPtr std::shared_ptr UseCount: " << my_memory.use_count() << std::endl;
7 }

執行結果爲:

Simple: 1

TestWeakPtr std::shared_ptr UseCount: 1

TestWeakPtr std::shared_ptr UseCount: 1

~Simple: 1

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

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

5. std::unique_ptr

std::unique_ptr是一個獨享全部權的智能指針,它提供了一種嚴格語義上的全部權,包括:

           i.  擁有它所指向的對象

           ii.  沒法進行復制、賦值操做(例題3)

           iii. 保存指向某個對象的指針,當它自己被刪除釋放的時候,會使用給定的刪除器釋放它指向的對象

           iv.  具備移動(std::move)語義,可作爲容器元素

 3、總結

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

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

二、在對象須要共享的狀況下,使用 std::shared_ptr.

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

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

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

[1]參見《effective C++》,條款06 。

相關文章
相關標籤/搜索