1. 程式人生 > >C++ 11 智慧指標的坑和引用計數的意義

C++ 11 智慧指標的坑和引用計數的意義

一、本節內容

本節內容包括:

  • 對標準庫的擴充: 智慧指標和引用計數
    • RAII 與引用計數
    • std::shared_ptr
    • std::unique_ptr
    • std::weak_ptr

二、RAII 與引用計數

瞭解 Objective-C/Swift 的程式設計師應該知道引用計數的概念。引用計數這種計數是為了防止記憶體洩露而產生的。基本想法是對於動態分配的物件,進行引用計數,每當增加一次對同一個物件的引用,那麼引用物件的引用計數就會增加一次,每刪除一次引用,引用計數就會減一,當一個物件的引用計數減為零時,就自動刪除指向的堆記憶體。

在傳統 C++ 中,『記得』手動釋放資源,總不是最佳實踐。因為我們很有可能就忘記了去釋放資源而導致洩露。所以通常的做法是對於一個物件而言,我們在建構函式的時候申請空間,而在解構函式(在離開作用域時呼叫)的時候釋放空間,也就是我們常說的 RAII 資源獲取即初始化技術。

凡事都有例外,我們總會有需要將物件在自由儲存上分配的需求,在傳統 C++ 裡我們只好使用 new 和 delete 去『記得』對資源進行釋放。而 C++11 引入了智慧指標的概念,使用了引用計數的想法,讓程式設計師不再需要關心手動釋放記憶體。這些智慧指標就包括 std::shared_ptr/std::unique_ptr/std::weak_ptr,使用它們需要包含標頭檔案 <memory>

注意:引用計數不是垃圾回收,引用技術能夠儘快收回不再被使用的物件,同時在回收的過程中也不會造成長時間的等待,更能夠清晰明確的表明資源的生命週期。

三、std::shared_ptr

std::shared_ptr

 是一種智慧指標,它能夠記錄多少個 shared_ptr 共同指向一個物件,從而消除顯示的呼叫 delete,當引用計數變為零的時候就會將物件自動刪除。

但還不夠,因為使用 std::shared_ptr 仍然需要使用 new 來呼叫,這使得程式碼出現了某種程度上的不對稱。

std::make_shared 就能夠用來消除顯示的使用 new,所以std::make_shared 會分配建立傳入引數中的物件,並返回這個物件型別的std::shared_ptr指標。例如:

  1. #include<iostream>
  2. #include<memory>
  3. voidfoo(std::shared_ptr
    <int> i)
  4. {
  5. (*i)++;
  6. }
  7. intmain()
  8. {
  9. // auto pointer = new int(10); // 非法, 不允許直接賦值
  10. // 構造了一個 std::shared_ptr
  11. auto pointer = std::make_shared<int>(10);
  12. foo(pointer);
  13. std::cout << *pointer << std::endl; // 11
  14. // 離開作用域前,shared_ptr 會被析構,從而釋放記憶體
  15. return 0;
  16. }

std::shared_ptr 可以通過 get() 方法來獲取原始指標,通過 reset() 來減少一個引用計數,並通過get_count()來檢視一個物件的引用計數。例如:

  1. auto pointer = std::make_shared<int>(10);
  2. auto pointer2 = pointer; // 引用計數+1
  3. auto pointer3 = pointer; // 引用計數+1
  4. int *p = pointer.get(); // 這樣不會增加引用計數
  5. std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
  6. std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
  7. std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
  8. pointer2.reset();
  9. std::cout << "reset pointer2:" << std::endl;
  10. std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
  11. std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset
  12. std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
  13. pointer3.reset();
  14. std::cout << "reset pointer3:" << std::endl;
  15. std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
  16. std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
  17. std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已 reset

四、std::unique_ptr

std::unique_ptr 是一種獨佔的智慧指標,它禁止其他智慧指標與其共享同一個物件,從而保證了程式碼的安全:

  1. std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 從 C++14 引入
  2. std::unique_ptr<int> pointer2 = pointer; // 非法

make_unique 並不複雜,C++11 沒有提供 std::make_unique,可以自行實現:

  1. template<typename T, typename ...Args>
  2. std::unique_ptr<T> make_unique( Args&& ...args ) {
  3. return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
  4. }

至於為什麼沒有提供,C++ 標準委員會主席 Herb Sutter 在他的部落格中提到原因是因為『被他們忘記了』。

既然是獨佔,換句話說就是不可複製。但是,我們可以利用 std::move 將其轉移給其他的 unique_ptr,例如:

  1. #include<iostream>
  2. #include<memory>
  3. struct Foo {
  4. Foo() { std::cout << "Foo::Foo" << std::endl; }
  5. ~Foo() { std::cout << "Foo::~Foo" << std::endl; }
  6. voidfoo(){ std::cout << "Foo::foo" << std::endl; }
  7. };
  8. voidf(const Foo &){
  9. std::cout << "f(const Foo&)" << std::endl;
  10. }
  11. intmain(){
  12. std::unique_ptr<Foo> p1(std::make_unique<Foo>());
  13. // p1 不空, 輸出
  14. if (p1) p1->foo();
  15. {
  16. std::unique_ptr<Foo> p2(std::move(p1));
  17. // p2 不空, 輸出
  18. f(*p2);
  19. // p2 不空, 輸出
  20. if(p2) p2->foo();
  21. // p1 為空, 無輸出
  22. if(p1) p1->foo();
  23. p1 = std::move(p2);
  24. // p2 為空, 無輸出
  25. if(p2) p2->foo();
  26. std::cout << "p2 被銷燬" << std::endl;
  27. }
  28. // p1 不空, 輸出
  29. if (p1) p1->foo();
  30. // Foo 的例項會在離開作用域時被銷燬
  31. }

五、std::weak_ptr

如果你仔細思考 std::shared_ptr 就會發現依然存在著資源無法釋放的問題。看下面這個例子:

  1. #include<iostream>
  2. #include<memory>
  3. class A;
  4. class B;
  5. class A {
  6. public:
  7. std::shared_ptr<B> pointer;
  8. ~A() {
  9. std::cout << "A 被銷燬" << std::endl;
  10. }
  11. };
  12. class B {
  13. public:
  14. std::shared_ptr<A> pointer;
  15. ~B() {
  16. std::cout << "B 被銷燬" << std::endl;
  17. }
  18. };
  19. intmain(){
  20. std::shared_ptr<A> a = std::make_shared<A>();
  21. std::shared_ptr<B> b = std::make_shared<B>();
  22. a->pointer = b;
  23. b->pointer = a;
  24. return 0;
  25. }

執行結果是 A, B 都不會被銷燬,這是因為 a,b 內部的 pointer 同時又引用了 a,b,這使得 a,b 的引用計數均變為了 2,而離開作用域時,a,b 智慧指標被析構,卻智慧造成這塊區域的引用計數減一,這樣就導致了 a,b 物件指向的記憶體區域引用計數不為零,而外部已經沒有辦法找到這塊區域了,也就造成了記憶體洩露,如圖所示:

此處輸入圖片的描述

解決這個問題的辦法就是使用弱引用指標 std::weak_ptrstd::weak_ptr是一種弱引用(相比較而言 std::shared_ptr 就是一種強引用)。弱引用不會引起引用計數增加,當換用弱引用時候,最終的釋放流程如下圖所示:

此處輸入圖片的描述

在上圖中,最後一步只剩下 B,而 B 並沒有任何智慧指標引用它,因此這塊記憶體資源也會被釋放。

std::weak_ptr 沒有 * 運算子和 -> 運算子,所以不能夠對資源進行操作,它的唯一作用就是用於檢查 std::shared_ptr是否存在,expired() 方法在資源未被釋放時,會返回 true,否則返回 false

正確的程式碼如下:

  1. #include<iostream>
  2. 相關推薦

    C++ 11 智慧指標引用計數意義

    一、本節內容本節內容包括:對標準庫的擴充: 智慧指標和引用計數RAII 與引用計數std::shared_ptrstd::unique_ptrstd::weak_ptr二、RAII 與引用計數瞭解 Objective-C/Swift 的程式設計師應該知道引用計數的概念。引用計

    C++11智慧指標引用

    最近在學習課程的時候發現一個很困惑的問題,上程式碼 class DataHeader; class LoginResult:public DataHeader; typedef std::shared_ptr<DataHeader> DataHeaerPtr; //原型 void addS

    C++11--智慧指標shared_ptr,weak_ptr,unique_ptr

    共享指標 shared_ptr /*********** Shared_ptr ***********/ // 為什麼要使用智慧指標,直接使用裸指標經常會出現以下情況 // 1. 當指標的生命長於所指的資源:野指標 // 2. 當指標的生命短於所指的資源:資源洩漏 // // 智慧指標: 確保指標和資源的生

    C++11--智慧指標詳解及實現

    1、基礎概念 智慧指標的一種通用實現技術是使用引用計數。智慧指標類將一個計數器與智慧指標指向的物件相關聯,用來記錄有多少個智慧指標指向相同的物件,並在恰當的時候釋放物件。 每次建立類的新物件時,初始化指標並將引用計數置為1;當物件作為另一物件的副本而建立時,引用計數加1;對一個物件進行賦

    c++11 智慧指標淺析

    之前學習c++沒有接觸到c++11的一些新的特性,最近啊研究boost庫的時候有接觸到新的東西出來,所以看了一下智慧指標的內容。 c++智慧指標的優勢: 相對於我們傳統的指標在new之後堆區的空間不知該什麼時候delete掉,所以c++11推出了智慧指標。更加方便對指標所指向的資源進行保護和

    c++11智慧指標解析——揭開底層面紗,完整理解智慧指標

    昨天跟同事小小的研究了下關於不同平臺下的位元組對齊問題,起因是遇到了一個坑,vs上沒有問題,在安卓上卻崩潰了。找了半天后發現是c++位元組補齊問題,期間包括使用#pragma pack(1)來限定位元組對齊方式等各種條件,也是把我們搞的七暈八素,總算是進一步瞭解了c++物件結構以及編譯器的操作(有機會的話

    使用 C++11 智慧指標時要避開的 10 大錯誤

    http://blog.jobbole.com/104666/ 我很喜歡新的C++11的智慧指標。在很多時候,對很多討厭自己管理記憶體的人來說是天賜的禮物。在我看來,C++11的智慧指標能使得C++新手教學更簡單。 其實,我已經使用C++11兩年多了,我無意中發現多種錯誤

    C++11智慧指標之使用shared_ptr實現多型

    指標除了管理記憶體之外,在C++中還有一個重要的功能就是實現多型。 程式碼很簡單,還是使用虛擬函式。與原生指標並沒有什麼區別: #include <iostream> #include &

    C++ 11 智慧指標淺析

    定義 為了實現指標自動回收的物件,表現和指標一樣,實際上它利用了棧的機制,每一個智慧指標都是一個模板類,呼叫智慧指標實際上是建立了一個智慧指標的物件,物件生命週期到達盡頭的時候,會自動呼叫智慧指標的解構函式,在解構函式裡,釋放掉它管理的記憶體,從而避免手動de

    C++11 智慧指標std::shared_ptr/std::unique_ptr/std::weak_ptr

    std::shared_ptr std::shared_ptr 是一種智慧指標,它能夠記錄多少個 shared_ptr 共同指向一個物件,從而消除顯示的呼叫 delete,當引用計數變為零的時候就

    智慧指標例項(引用計數型)

    如果在多個類的例項中共享同一塊堆記憶體,指標的操作會相當繁瑣,一不小心就會出現野指標,試想一塊記憶體被其中一個指標釋放了,另外幾個物件的指標仍然指向他的情況,相當可怕!這時候就需要用帶有引用計數的智慧指標管理了! 《C++ Primer》上給出了兩種實現方式: 1.利用友元

    C++11 智慧指標之 std::shared_ptr 初級學習

    shared_ptr概念 shared_ptr基於“引用計數”模型實現,多個shared_ptr可指向同一個動態物件,並維護了一個共享的引用計數器,記錄了引用同一物件的shared_ptr例項的數量。當最後一個指向動態物件的shared_ptr銷燬時,會自動

    C++11——智慧指標

    1. 介紹   一般一個程式在記憶體中可以大體劃分為三部分——靜態記憶體(區域性的static物件、類static資料成員以及所有定義在函式或者類之外的變數)、棧記憶體(儲存和定義在函式或者類內部的變數)和動態記憶體(實質上這塊記憶體池就是堆,通常通過new/malloc操作申請的

    C++】智慧指標引用計數的實現

    在C++11的標準中,引入了智慧指標的概念。 相比於auto_ptr而言,其主要缺陷在於在進行指標拷貝的時候,會出現管理權轉移的問題,導致原指標最終編成一個懸掛指標(dangling pointer

    C++11 圖說VS2013下的引用疊加規則模板參數類型推導規則

    反匯編 cto 構造 這不 gif 怎麽辦 由於 pla 覆蓋 背景: 最近在學習C++STL,出於偶然,在C++Reference上看到了vector下的emplace_back函數,不想由此引發了一系列的“探索”,於是就有了現在這篇博文。 前言:

    C++之智慧指標std::shared_ptr簡單使用理解

    1  智慧指標std::shared_ptr相關知識和如何使用 我們這裡先說下智慧指標std::shared_ptr,因為我看到我我們專案c++程式碼裡面用得很多,我不是不會,所以記錄學習下 先讓ubuntu終端支援c++11,如果自己的電腦還沒配置號,可以先看下我的這篇部落格

    C++之智慧指標普通指標單例模式兩種實現

    1  問題 實現c++的單例模式,這裡測試分別寫了通過智慧指標返回物件和普通返回指標     2  程式碼測試 include <iostream> #include <mutex> #include <m

    c++ 之智慧指標:儘量使用std::make_uniquestd::make_shared而不直接使用new

    關於make_unique的構造及使用例程,MSDN的講解非常詳細 (https://msdn.microsoft.com/zh-cn/library/dn439780.aspx ) 使用過程中,主要有這麼幾個關鍵點: 1.  make_unique 同 uni

    《深入理解C++11》筆記–右值引用:移動語義完美轉發

    上一篇:《深入理解C++11》筆記–建構函式 這篇文章介紹的了第三章中右值引用相關的內容。在介紹該內容之前,會對一些相關問題進行解釋,便於理解後面的內容。 並且,提前說明,許多編譯器會多拷貝構造和移動構造進行優化省略,這樣就看不到拷貝構造和移動構造的過程,需

    C++中智慧指標的設計使用

    原文地址:http://blog.csdn.net/hackbuteer1/article/details/7561235      智慧指標(smart pointer)是儲存指向動態分配(堆)物件指標的類,用於生存期控制,能夠確保自動正確的銷燬動態分配的物件,防止記憶體