智慧指標之 shared_ptr 的使用
一、智慧指標
1. 什麼是智慧指標
簡單地說,C++智慧指標是包含過載運算子的類,其行為像常規指標,但智慧指標能夠及時、妥善地銷燬動態分配的資料,並實現了明確的物件生命週期,因此更有價值。
2. 常規指標存在的問題
C++在記憶體分配、釋放和管理方面向程式猿提供了全面的靈活性。但是這種靈活性是把雙刃劍,一方面它使C++成為一種功能強大的語言,另一方面它讓程式猿能夠製造與記憶體相關的問題,比如記憶體洩漏。
例如在堆宣告和分配的記憶體,析構方法是否會自動銷燬物件,又或是方法結束後需要一個個釋放,方法存在很多返回的地方,每個返回語句都要執行很多相關釋放操作,十分繁瑣,用智慧指標就能實現其自動釋放。
3. 智慧指標有何幫助
鑑於使用常規指標存在的問題,當 C++ 程式猿需要管理堆中的資料時,可使用智慧指標的方式分配和管理記憶體。
4. 智慧指標是如何實現的
智慧指標類過載瞭解除引用運算子(*)和成員選擇運算子(->)。
同時為了能夠在堆中管理各種型別,幾乎所有的智慧指標都是模板類,包含其功能的泛型實現。
二、shared_ptr 簡介
1. 概念
shared_ptr
是一種定義在 <memory>
中的智慧指標(smart pointer)。和 unique_ptr
不同,它允許多個指標指向同一個物件。
由於舊的 auto_ptr
存在一些問題:不能指向陣列,不能共享所有權,不能通過複製操作初始化,不能放入容器中使用,不能作為容器成員,容易重複 delete 物件…
所以新設計的 shared_ptr
auto_ptr
,任何時候我們都應該使用 shared_ptr
而不是 auto_ptr
。
2. 原理
shared_ptr
使用引用計數的概念,每當一個指標指向同一個物件時,計數加一。在 shared_ptr
析構的時候,當且僅當計數為零時才會去刪除指向的物件資源,否則只是將計數減一。
3. 示例
下面簡單演示了一下 shared_ptr
的使用。注意當我們使用一個物件資源建立智慧指標後,後續的操作應該都是通過這個智慧指標來進行。
#include <iostream>
#include <memory>
int main() {
int * i = new int(1);
std::shared_ptr<int> p1(i); // 建立共享指標 p1
std::cout << "p1 count: " << p1.use_count() << "\n"; // count: 1
{
std::shared_ptr<int> p2(p1); // 建立共享指標 p2,它和 p1 共享一個引用計數器,計數加一
std::cout << "p1 count: " << p1.use_count() << "\n"; // count: 2
std::cout << "p2 count: " << p2.use_count() << "\n"; // count: 2
// 離開作用域,p2 被析構,計數減一
}
std::cout << "p1 count: " << p1.use_count() << "\n"; // count: 1
std::shared_ptr<int> p3(i); // 注意:建立共享指標 p3,這樣寫的話沒有和 p1 共享引用計數器
std::cout << "p1 count: " << p1.use_count() << "\n"; // count: 1
std::cout << "p3 count: " << p3.use_count() << "\n"; // count: 1
// 注意:這樣物件 i 將在 p1 和 p3 析構的時候釋放兩次而出錯!
return 0;
}
三、shared_ptr 各方法介紹
1. 構建方法
#include <iostream>
#include <memory>
struct C { int* data; };
int main() {
std::shared_ptr<int> p1;
std::shared_ptr<int> p2(nullptr);
std::shared_ptr<int> p3(new int);
std::shared_ptr<int> p4(new int, std::default_delete<int>());
std::shared_ptr<int> p5(new int, [](int* p) {delete p; }, std::allocator<int>());
std::shared_ptr<int> p6(p5);
std::shared_ptr<int> p7(std::move(p6));
std::shared_ptr<int> p8(std::unique_ptr<int>(new int));
std::shared_ptr<C> obj(new C);
std::shared_ptr<int> p9(obj, obj->data);
// make_shared函式的主要功能是在動態記憶體中分配一個物件並初始化它,並返回指向此物件的shared_ptr
std::shared_ptr<int> p10 = std::make_shared<int>(1);
std::cout << "use_count:\n";
std::cout << "p1: " << p1.use_count() << '\n'; // 0
std::cout << "p2: " << p2.use_count() << '\n'; // 0
std::cout << "p3: " << p3.use_count() << '\n'; // 1
std::cout << "p4: " << p4.use_count() << '\n'; // 1
std::cout << "p5: " << p5.use_count() << '\n'; // 2
std::cout << "p6: " << p6.use_count() << '\n'; // 0
std::cout << "p7: " << p7.use_count() << '\n'; // 2
std::cout << "p8: " << p8.use_count() << '\n'; // 1
std::cout << "p9: " << p9.use_count() << '\n'; // 2
std::cout << "p10: " << p10.use_count() << '\n'; // 1
}
2. use_count() 獲取引用計數個數
3. unique() 返回是否獨佔,即判斷 use_count 是否為 1
4. swap() 交換兩個 shared_ptr 擁有的物件
5. reset() 放棄內部物件的擁有權,即原物件的引用計數減一
四、自定義刪除器
我們可以為 shared_ptr
設定要使用的刪除器,即物件釋放時需要執行的方法。
#include <iostream>
#include <memory>
class A {};
void Deleter(A* obj) {
delete obj; // 刪除物件指標,根據具體需求改寫釋放的實現
}
int main() {
std::shared_ptr<int> p1(new int[10], [](int *p) {delete[] p; }); // 刪除陣列一定要使用 delete[]
std::shared_ptr<A> p2(new A, Deleter); // 使用指定的刪除器
}
五、其它
1. unique_ptr
當資源物件只需要被引用一次,推薦使用 unique_ptr
。關於 unique_ptr
可以參見:
https://blog.csdn.net/afei__/article/details/80670283
2. weak_ptr
weak_ptr
的出現是伴隨 shared_ptr
而來的,專門用於解決 迴圈引用 帶來的記憶體洩漏的問題。
當兩個 shared_ptr
互相引用的時候,將導致迴圈引用的問題,造成無法釋放產生記憶體洩漏。
通過將其中一個 shared_ptr
替換為 weak_ptr
可解決該問題,因為 weak_ptr
不會增加引用計數。