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 行代碼:

#include <memory>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> 類型的空智能指針:


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

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

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

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

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

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

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

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

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

//調用拷貝構造函數std::shared_ptr<int> p4(p3);//或者 std::shared_ptr<int> p4 = p3;//調用移動構造函數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 對象賦值,否則會導致程序發生異常。例如:



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

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

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

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



//指定 default_delete 作爲釋放規則std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());//自定義釋放規則void deleteInt(int*p) {delete []p;}//初始化智能指針,並自定義釋放規則std::shared_ptr<int> p7(new int[10], deleteInt);

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

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 智能指針的基本用法,以及該模板類提供了一些成員方法的用法:

#include <iostream>#include <memory>using namespace std;int main(){    //構建 2 個智能指針    std::shared_ptr<int> p1(new int(10));    std::shared_ptr<int> p2(p1);    //輸出 p2 指向的數據    cout << *p2 << endl;    p1.reset();//引用計數減 1,p1爲空指針    if (p1) {        cout << "p1 不爲空" << endl;    }    else {        cout << "p1 爲空" << endl;    }    //以上操作,並不會影響 p2    cout << *p2 << endl;    //判斷當前和 p2 同指向的智能指針有多少個    cout << p2.use_count() << endl;    return 0;}

程序執行結果爲:

10
p1 爲空
10
1


圖片


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章