C++11 shared_ptr智能指針(超級詳細)


在實際的 C++ 開發中,我們經常會遇到諸如程序運行中突然崩潰、程序運行所用內存越來越多最終不得不重啟等問題,這些問題往往都是內存資源管理不當造成的。比如:

  • 有些內存資源已經被釋放,但指向它的指針並沒有改變指向(成為了野指針),並且后續還在使用;
  • 有些內存資源已經被釋放,后期又試圖再釋放一次(重復釋放同一塊內存會導致程序運行崩潰);
  • 沒有及時釋放不再使用的內存資源,造成內存泄漏,程序占用的內存資源越來越多。


針對以上這些情況,很多程序員認為 C++ 語言應該提供更友好的內存管理機制,這樣就可以將精力集中於開發項目的各個功能上。

事實上,顯示內存管理的替代方案很早就有了,早在 1959 年前后,就有人提出了“垃圾自動回收”機制。所謂垃圾,指的是那些不再使用或者沒有任何指針指向的內存空間,而“回收”則指的是將這些“垃圾”收集起來以便再次利用。

如今,垃圾回收機制已經大行其道,得到了諸多編程語言的支持,例如 Java、Python、C#、PHP 等。而 C++ 雖然從來沒有公開得支持過垃圾回收機制,但 C++98/03 標准中,支持使用 auto_ptr 智能指針來實現堆內存的自動回收;C++11 新標准在廢棄 auto_ptr 的同時,增添了 unique_ptr、shared_ptr 以及 weak_ptr 這 3 個智能指針來實現堆內存的自動回收。

所謂智能指針,可以從字面上理解為“智能”的指針。具體來講,智能指針和普通指針的用法是相似的,不同之處在於,智能指針可以在適當時機自動釋放分配的內存。也就是說,使用智能指針可以很好地避免“忘記釋放內存而導致內存泄漏”問題出現。由此可見,C++ 也逐漸開始支持垃圾回收機制了,盡管目前支持程度還有限。

C++ 智能指針底層是采用引用計數的方式實現的。簡單的理解,智能指針在申請堆內存空間的同時,會為其配備一個整形值(初始值為 1),每當有新對象使用此堆內存時,該整形值 +1;反之,每當使用此堆內存的對象被釋放時,該整形值減 1。當堆空間對應的整形值為 0 時,即表明不再有對象使用它,該堆空間就會被釋放掉。

接下來,我們將分別對 shared_ptr、unique_ptr 以及 weak_ptr 這 3 個智能指針的特性和用法做詳細的講解,本節先介紹 shared_ptr 智能指針。

C++11 shared_ptr智能指針

實際上,每種智能指針都是以類模板的方式實現的,shared_ptr 也不例外。shared_ptr<T>(其中 T 表示指針指向的具體數據類型)的定義位於<memory>頭文件,並位於 std 命名空間中,因此在使用該類型指針時,程序中應包含如下 2 行代碼:

  1. #include <memory>
  2. using namespace std;

注意,第 2 行代碼並不是必須的,也可以不添加,則后續在使用 shared_ptr 智能指針時,就需要明確指明std::

值得一提的是,和 unique_ptr、weak_ptr 不同之處在於,多個 shared_ptr 智能指針可以共同使用同一塊堆內存。並且,由於該類型智能指針在實現上采用的是引用計數機制,即便有一個 shared_ptr 指針放棄了堆內存的“使用權”(引用計數減 1),也不會影響其他指向同一堆內存的 shared_ptr 指針(只有引用計數為 0 時,堆內存才會被自動釋放)。

1、shared_ptr智能指針的創建

shared_ptr<T> 類模板中,提供了多種實用的構造函數,這里給讀者列舉了幾個常用的構造函數(以構建指向 int 類型數據的智能指針為例)。

1)  通過如下 2 種方式,可以構造出 shared_ptr<T> 類型的空智能指針:

  1. std::shared_ptr<int> p1; //不傳入任何實參
  2. std::shared_ptr<int> p2(nullptr); //傳入空指針 nullptr

注意,空的 shared_ptr 指針,其初始引用計數為 0,而不是 1。

2) 在構建 shared_ptr 智能指針,也可以明確其指向。例如:

  1. std::shared_ptr<int> p3(new int(10));

由此,我們就成功構建了一個 shared_ptr 智能指針,其指向一塊存有 10 這個 int 類型數據的堆內存空間。

同時,C++11 標准中還提供了 std::make_shared<T> 模板函數,其可以用於初始化 shared_ptr 智能指針,例如:

  1. std::shared_ptr<int> p3 = std::make_shared<int>(10);

以上 2 種方式創建的 p3 是完全相同。

3) 除此之外,shared_ptr<T> 模板還提供有相應的拷貝構造函數和移動構造函數,例如:

  1. //調用拷貝構造函數
  2. std::shared_ptr<int> p4(p3);//或者 std::shared_ptr<int> p4 = p3;
  3. //調用移動構造函數
  4. std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr<int> p5 = std::move(p4);

有關拷貝構造函數,讀者可閱讀《C++拷貝構造函數》一節做系統了解;有關移動構造函數,讀者可閱讀《C++移動構造函數》做詳細了解;有關 move() 函數的功能和用法,讀者可閱讀《C++11 move()》一節。

如上所示,p3 和 p4 都是 shared_ptr 類型的智能指針,因此可以用 p3 來初始化 p4,由於 p3 是左值,因此會調用拷貝構造函數。需要注意的是,如果 p3 為空智能指針,則 p4 也為空智能指針,其引用計數初始值為 0;反之,則表明 p4 和 p3 指向同一塊堆內存,同時該堆空間的引用計數會加 1。

而對於 std::move(p4) 來說,該函數會強制將 p4 轉換成對應的右值,因此初始化 p5 調用的是移動構造函數。另外和調用拷貝構造函數不同,用 std::move(p4) 初始化 p5,會使得 p5 擁有了 p4 的堆內存,而 p4 則變成了空智能指針。

注意,同一普通指針不能同時為多個 shared_ptr 對象賦值,否則會導致程序發生異常。例如:

  1. int* ptr = new int;
  2. std::shared_ptr<int> p1(ptr);
  3. std::shared_ptr<int> p2(ptr);//錯誤


4) 在初始化 shared_ptr 智能指針時,還可以自定義所指堆內存的釋放規則,這樣當堆內存的引用計數為 0 時,會優先調用我們自定義的釋放規則。

在某些場景中,自定義釋放規則是很有必要的。比如,對於申請的動態數組來說,shared_ptr 指針默認的釋放規則是不支持釋放數組的,只能自定義對應的釋放規則,才能正確地釋放申請的堆內存。

對於申請的動態數組,釋放規則可以使用 C++11 標准中提供的 default_delete<T> 模板類,我們也可以自定義釋放規則:

  1. //指定 default_delete 作為釋放規則
  2. std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());
  3. //自定義釋放規則
  4. void deleteInt(int*p) {
  5. delete []p;
  6. }
  7. //初始化智能指針,並自定義釋放規則
  8. std::shared_ptr<int> p7(new int[10], deleteInt);

實際上借助 lambda 表達式,我們還可以像如下這樣初始化 p7,它們是完全相同的:

  1. std::shared_ptr<int> p7(new int[10], [](int* p) {delete[]p; });

shared_ptr<T> 模板類還提供有其它一些初始化智能指針的方法,感興趣的讀者可前往講解 shared_ptr 的官網做系統了解。

2、shared_ptr<T>模板類提供的成員方法

為了方便用戶使用 shared_ptr 智能指針,shared_ptr<T> 模板類還提供有一些實用的成員方法,它們各自的功能如表 1 所示。

表 1 shared_ptr<T>模板類常用成員方法
成員方法名 功 能
operator=() 重載賦值號,使得同一類型的 shared_ptr 智能指針可以相互賦值。
operator*() 重載 * 號,獲取當前 shared_ptr 智能指針對象指向的數據。
operator->() 重載 -> 號,當智能指針指向的數據類型為自定義的結構體時,通過 -> 運算符可以獲取其內部的指定成員。
swap() 交換 2 個相同類型 shared_ptr 智能指針的內容。
reset() 當函數沒有實參時,該函數會使當前 shared_ptr 所指堆內存的引用計數減 1,同時將當前對象重置為一個空指針;當為函數傳遞一個新申請的堆內存時,則調用該函數的 shared_ptr 對象會獲得該存儲空間的所有權,並且引用計數的初始值為 1。
get() 獲得 shared_ptr 對象內部包含的普通指針。
use_count() 返回同當前 shared_ptr 對象(包括它)指向相同的所有 shared_ptr 對象的數量。
unique() 判斷當前 shared_ptr 對象指向的堆內存,是否不再有其它 shared_ptr 對象再指向它。
operator bool() 判斷當前 shared_ptr 對象是否為空智能指針,如果是空指針,返回 false;反之,返回 true。

 除此之外,C++11 標准還支持同一類型的 shared_ptr 對象,或者 shared_ptr 和 nullptr 之間,進行 ==,!=,<,<=,>,>= 運算。


下面程序給大家演示了 shared_ptr 智能指針的基本用法,以及該模板類提供了一些成員方法的用法:

  1. #include <iostream>
  2. #include <memory>
  3. using namespace std;
  4. int main()
  5. {
  6. //構建 2 個智能指針
  7. std::shared_ptr<int> p1(new int(10));
  8. std::shared_ptr<int> p2(p1);
  9. //輸出 p2 指向的數據
  10. cout << *p2 << endl;
  11. p1.reset();//引用計數減 1,p1為空指針
  12. if (p1) {
  13. cout << "p1 不為空" << endl;
  14. }
  15. else {
  16. cout << "p1 為空" << endl;
  17. }
  18. //以上操作,並不會影響 p2
  19. cout << *p2 << endl;
  20. //判斷當前和 p2 同指向的智能指針有多少個
  21. cout << p2.use_count() << endl;
  22. return 0;
  23. }

程序執行結果為:

10
p1 為空
10
1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM