1. 程式人生 > >智慧指標之 shared_ptr 的使用

智慧指標之 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 不會增加引用計數。