C++ 11 智慧指標淺析
定義
為了實現指標自動回收的物件,表現和指標一樣,實際上它利用了棧的機制,每一個智慧指標都是一個模板類,呼叫智慧指標實際上是建立了一個智慧指標的物件,物件生命週期到達盡頭的時候,會自動呼叫智慧指標的解構函式,在解構函式裡,釋放掉它管理的記憶體,從而避免手動delete。
Java裡有類似實現(jdk1.2以後有strong,soft,weak,phantom引用)
C++11之前有auto_ptr,但是與STL不相容,C++11時被棄用
shared_ptr是引用計數的智慧指標,而unique_ptr不是。這意味著,可以有多個shared_ptr例項指向同一塊動態分配的記憶體,當最後一個shared_ptr離開作用域時,才會釋放這塊記憶體。shared_ptr也是執行緒安全的。
預設的智慧指標應該是unique_ptr。只有需要共享資源時,才使用shared_ptr,weak_ptr是shared_ptr的弱引用,不增加其引用計數器。
指標 | 簡要描述 |
---|---|
shared_ptr | 允許多個指標指向同一個物件 |
unique_ptr | 獨佔所指向的物件 |
weak_ptr | shared_ptr的弱引用 |
share_ptr
允許多個shared_ptr指向同一個物件,使用引用計數器來判斷是否需要釋放。訪問時像C++一般的指標一樣。
最安全和高效的方法是呼叫make_shared庫函式,該函式會在堆中分配一個物件並初始化,最後返回指向此物件的share_ptr例項。如果你不想使用make_ptr,也可以先明確new出一個物件,然後把其原始指標傳遞給share_ptr的建構函式。
可以*pInt
也可以pInt->
。指標的拷貝p=q會使q的引用計數++,p的–,如下:
#include <iostream>
#include <memory>
using namespace std;
class Example
{
public:
Example() : e(1) { cout << "Example Constructor..." << endl; }
~Example() { cout << "Example Destructor..." << endl; }
int e;
};
int main() {
shared_ptr<Example> pInt(new Example());
shared_ptr<Example> pInt3=make_shard<Example>();
auto pInt4=pInt3;
cout << (*pInt).e << endl;
cout << "pInt引用計數: " << pInt.use_count() << endl;
shared_ptr<Example> pInt2 = pInt;
cout << "pInt引用計數: " << pInt.use_count() << endl;
cout << "pInt2引用計數: " << pInt2.use_count() << endl;
cout << "pInt3引用計數: " << pInt3.use_count() << endl;
cout << "pInt4引用計數: " << pInt4.use_count() << endl;
pInt3=pInt;
cout << "pInt引用計數: " << pInt.use_count() << endl;
cout << "pInt2引用計數: " << pInt2.use_count() << endl;
cout << "pInt3引用計數: " << pInt3.use_count() << endl;
cout << "pInt4引用計數: " << pInt4.use_count() << endl;
}
Example Constructor...
Example Constructor...
1
pInt引用計數: 1
pInt引用計數: 2
pInt2引用計數: 2
pInt3引用計數: 2
pInt4引用計數: 2
pInt引用計數: 3
pInt2引用計數: 3
pInt3引用計數: 3
pInt4引用計數: 1
Example Destructor...
Example Destructor...
shared_ptr提供了兩個函式來檢查其共享的引用計數值,分別是unique()和use_count()。
在前面,我們已經多次使用過use_count()函式,該函式返回當前指標的引用計數值。值得注意的是use_count()函式可能效率很低,應該只把它用於測試或除錯。
unique()函式用來測試該shared_ptr是否是原始指標唯一擁有者,也就是use_count()的返回值為1時返回true,否則返回false。
unique_ptr
與shared_ptr不同,某一時刻,只能有一個unique_ptr指向一個給定的物件。因此,當unique_ptr被銷燬,它所指的物件也會被銷燬。unique_ptr沒有類似make_shared函式只能通過unique_ptr<int> pInt(new int(5));
無法複製構造和賦值。但是可以從函式中返回。也可以同過指標操作。類似auto_ptr
可以進行移動構造和移動賦值操作,通過std::move()函式。
int main() {
unique_ptr<int> pInt(new int(5));
unique_ptr<int> pInt2 = std::move(pInt); // 轉移所有權
//cout << *pInt << endl; // 出錯,pInt為空
cout << *pInt2 << endl;
unique_ptr<int> pInt3(std::move(pInt2));
}
常見場景:
- 為動態申請的資源提供異常安全保證
void Func() { unique_ptr<int> p(new int(5)); // ...(可能會丟擲異常) }
- 返回函式內動態申請資源的所有權
unique_ptr<int> Func(int p) { unique_ptr<int> pInt(new int(p)); return pInt; // 返回unique_ptr } int main() { int p = 5; unique_ptr<int> ret = Func(p); cout << *ret << endl; // 函式結束後,自動釋放資源 }
- 在容器中儲存指標
int main() { vector<unique_ptr<int>> vec; unique_ptr<int> p(new int(5)); vec.push_back(std::move(p)); // 使用移動語義 }
- 管理動態陣列
int main() { unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5}); p[0] = 0; // 過載了operator[] }
- 作為auto_ptr的代替品
weak_ptr
使用計數器的shared_ptr很容易就產生迴圈引用的問題。這時如果用一個weak_ptr來指向shared_ptr不會增加他的引用計數器。可以使用lock()函式來安全地獲取一個weak_ptr的一個shared_ptr的引用,如果此時shared_ptr已被reset,則返回一個空的shared_ptr,賦值語句不成功。也可以安全地通過expired()函式來判斷是否過期。
使用時不能通過shared_ptr傳統指標操作,但是可以通過lock()來獲取他的share_ptr()。可用來解決迴圈引用的問題。
class ClassB;
class ClassA
{
public:
ClassA() { cout << "ClassA Constructor..." << endl; }
~ClassA() { cout << "ClassA Destructor..." << endl; }
weak_ptr<ClassB> pb; // 在A中引用B
};
class ClassB
{
public:
ClassB() { cout << "ClassB Constructor..." << endl; }
~ClassB() { cout << "ClassB Destructor..." << endl; }
weak_ptr<ClassA> pa; // 在B中引用A
};
int main() {
shared_ptr<ClassA> spa = make_shared<ClassA>();
shared_ptr<ClassB> spb = make_shared<ClassB>();
spa->pb = spb;
spb->pa = spa;
// 函式結束,思考一下:spa和spb會釋放資源麼?
}
ClassA Constructor...
ClassB Constructor...
ClassA Destructor...
ClassB Destructor...
Program ended with exit code: 0