C++11 shared_ptr(智慧指標)詳解

2020-07-16 10:04:31
要確保用 new 動態分配的記憶體空間在程式的各條執行路徑都能被釋放是一件麻煩的事情。C++ 11 模板庫的 <memory> 標頭檔案中定義的智慧指標,即 shared _ptr 模板,就是用來部分解決這個問題的。

只要將 new 運算子返回的指標 p 交給一個 shared_ptr 物件“託管”,就不必擔心在哪裡寫delete p語句——實際上根本不需要編寫這條語句,託管 p 的 shared_ptr 物件在消亡時會自動執行delete p。而且,該 shared_ptr 物件能像指標 p —樣使用,即假設托管 p 的 shared_ptr 物件叫作 ptr,那麼 *ptr 就是 p 指向的物件。

通過 shared_ptr 的建構函式,可以讓 shared_ptr 物件託管一個 new 運算子返回的指標,寫法如下:

shared_ptr<T> ptr(new T);  // T 可以是 int、char、類等各種型別

此後,ptr 就可以像 T* 型別的指標一樣使用,即 *ptr 就是用 new 動態分配的那個物件。

多個 shared_ptr 物件可以共同託管一個指標 p,當所有曾經託管 p 的 shared_ptr 物件都解除了對其的託管時,就會執行delete p

例如下面的程式:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
    int i;
    A(int n):i(n) { };
    ~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
    shared_ptr<A> sp1(new A(2)); //A(2)由sp1託管,
    shared_ptr<A> sp2(sp1);       //A(2)同時交由sp2託管
    shared_ptr<A> sp3;
    sp3 = sp2;   //A(2)同時交由sp3託管
    cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
    A * p = sp3.get();      // get返回託管的指標,p 指向 A(2)
    cout << p->i << endl;  //輸出 2
    sp1.reset(new A(3));    // reset導致託管新的指標, 此時sp1託管A(3)
    sp2.reset(new A(4));    // sp2託管A(4)
    cout << sp1->i << endl; //輸出 3
    sp3.reset(new A(5));    // sp3託管A(5),A(2)無人託管,被delete
    cout << "end" << endl;
    return 0;
}
程式的輸出結果如下:
2,2,2
2
3
2 destructed
end
5 destructed
4 destructed
3 destructed

可以用第 14 行及第 16 行的形式讓多個 sharecLptr 物件託管同一個指標。這多個 shared_ptr 物件會共用一個對共同託管的指標的“託管計數”。有 n 個 shared_ptr 物件託管同一個指標 p,則 p 的託管計數就是 n。當一個指標的託管計數減為 0 時,該指標會被釋放。shared_ptr 物件消亡或託管了新的指標,都會導致其原託管指標的託管計數減 1。

第 20、21 行,shared_ptr 的 reset 成員函數可以使得物件解除對原託管指標的託管(如果有的話),並託管新的指標。原指標的託管計數會減 1。

輸出的第 4 行說明,用 new 建立的動態物件 A(2) 被釋放了。程式中沒有寫 delete 語句,而 A(2) 被釋放,是因為程式的第 23 行執行後,已經沒有 shared_ptr 物件託管 A(2),於是 A(2) 的託管計數變為 0。最後一個解除對 A(2) 託管的 shared_ptr 物件會釋放 A(2)。

main 函數結束時,sp1、sp2、sp3 物件消亡,各自將其託管的指標的託管計數減為 0,並且釋放其託管的指標,於是會有以下輸出:
5 destructed
4 destructed
3 destructed

只有指向動態分配的物件的指標才能交給 shared_ptr 物件託管。將指向普通區域性變數、全域性變數的指標交給 shared_ptr 託管,編譯時不會有問題,但程式執行時會出錯,因為不能解構一個並沒有指向動態分配的記憶體空間的指標。

注意,不能用下面的方式使得兩個 shared_ptr 物件託管同一個指標:
A* p = new A(10);
shared_ptr <A> sp1(p), sp2(p);
sp1 和 sp2 並不會共用同一個對 p 的託管計數,而是各自將對 p 的託管計數都記為 1(sp2 無法知道 p 已經被 sp1 託管過)。這樣,當 sp1 消亡時要解構 p,sp2 消亡時要再次解構 p,這會導致程式崩潰。