1. 程式人生 > >C++小知識——記憶體管理之智慧指標

C++小知識——記憶體管理之智慧指標

在這裡插入圖片描述
在C++中,動態記憶體的管理是通過一對運算子來完成的:
1)new:在動態記憶體中為物件分配空間並返回一個指向該物件的指標
2)delete:接受一個動態物件的指標,銷燬該物件,並釋放與之關聯的記憶體
因為這樣的原因,我們時常很難確保在正確的時間去釋放記憶體,甚至我們忘記了去釋放記憶體,這樣就會產生記憶體洩漏,有時在尚有指標引用記憶體時,我們提前釋放了它,那麼這個時候就會產生引用非法記憶體的指標。

引言

為了更容易、更安全地使用動態記憶體,新標準庫提供了智慧指標型別來管理動態物件,均定義在memory標頭檔案中。

一、shared_ptr共享智慧指標

std::shared_ptr使用引用計數,每個shared_ptr的拷貝都指向相同的記憶體,資源被幾個指標共享(可以通過user_count()來檢視資源的所有者個數)

,在最後一個shared_ptr析構時記憶體才會釋放

1.1 基本使用

1.1.1 初始化方法

1)呼叫make_shared標準庫函式,此函式在動態記憶體中分配一個物件並初始化它,返回指向此物件的shared_ptr
2)通過new來構造

1.1.2 shared_ptr的拷貝和賦值

當進行拷貝或賦值操作時,每個shared_ptr都會記錄有多少個其他shared_ptr指向相同的物件。
1)遞增情況:
無論何時我們去拷貝一個shared_ptr,計數器都會遞增

  • 用一個shared_ptr去初始化另一個shared_ptr
  • 將它作為引數傳遞給一個函式時
  • 作為函式的返回值時

2)遞減情況:

  • 給shared_ptr賦予一個新的值的時候
  • shared_ptr被銷燬(例如一個區域性的shared_ptr離開其作用域)的時候
    一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的物件。

1.2 舉個栗子

在這裡插入圖片描述
在都reset之後,引用計數均變為0.

1.3 其他功能

通過get方法來獲取原指標,但不能釋放

std::shared_ptr<int> ptr(new int(10));
int *p = ptr.get();
delete p;		// error

指定刪除器,在指標引用為0的時候自動呼叫。支援普通函式和lambda表示式

// 普通函式
void DeleteIntPtr(int *p) {delete p;}
shared_ptr<int> p(new int(10), DeleteIntPtr);

// lambda表示式
shared_ptr<int> p(new int(10), [](int *p) {delete p;});

當智慧指標管理動態陣列的時候,預設的刪除器不支援陣列物件。需要指定刪除器,自定義刪除器或者使用改善的預設修改器都可以。

shared_ptr<int> p(new int[10], [](int *p) {delete[] p;}); // lambda
shared_ptr<int> p1(new int[10], default_delete<int []>); // 指定delete []

1.4 使用時要注意的問題

1)避免一個原始指標初始化多個shared_ptr

int* p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);

2)不要在引數實參中建立shared_ptr,而應該先建立,再用來做引數傳遞

func(shared_ptr<int>(new int), g());	// ERROR

shared_ptr<int> p(new int);
f(p, g());

二、unique_ptr獨佔智慧指標

2.1 初始化

unique_ptr是一個獨佔型智慧指標,與shared_ptr的不同:

  • 某個時刻智慧有一個unique_ptr指向一個給定物件。當unique_ptr被銷燬時,它所指向的物件也被銷燬
  • 沒有類似make_shared的標準庫函式返回一個unique_ptr。
  • 定義一個unique_ptr時,需要將其繫結到一個new返回的指標上。

幾個要注意的:

- 不允許其他的智慧指標共享其內部的指標
- 不允許通過賦值將一個unique_ptr賦值給另一個unique_ptr,即無法使兩個unique_ptr指向同一個物件
- 用move()函式把unique_ptr傳入來返回給其它的unique_ptr,轉移之後,不再對之前的指標具有所有權,即原先的unique_ptr已經失效了
- 初始化unique_ptr須採用直接初始化的形式,如unique_ptr p2(new int(42));`// p2指向一個值為42的物件
- 雖然不能拷貝或賦值unique_ptr,但可以通過release或reset將指標的所有權從一個unique_ptr轉到另一個unique_ptr,release成員返回unique_ptr當前儲存的指標並將其置為空

2.2 舉個栗子

在這裡插入圖片描述

2.3 其他功能

與shared_ptr不同,在指定刪除器時,必須指定刪除器型別

shared_ptr<int> ptr(new int(1), [](int *p){delete p;});           //ok

unique_ptr<int> ptr2(new int(1), [](int *p){delete p;});           //error
unique_ptr<int, void(*)(int *)> ptr2(new int(1), [](int *p){delete p;}); //ok

與shared_ptr不同,可以指向一個數組

unique_ptr<int []> ptr(new int[10]);   //ok
ptr[1] = 10;

shared_ptr<int []> ptr2(new int[10]); //error

三、weak_ptr弱引用智慧指標

weak_ptr弱引用智慧指標是用來解決相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那麼這兩個指標的引用計數永遠不可能下降為0,資源永遠不會釋放。它是對物件的一種弱引用,不會增加物件的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過呼叫lock函式來獲得shared_ptr

3.1 基本使用

觀察計數,通過use_count方法來獲得當前資源的引用計數

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

std::weak_ptr<int> wp(sp);
std::cout << wp.use_count() << std::endl; // 輸出1

std::shared_ptr<int> ssp(sp);
std::cout << ssp.use_count() << std::endl; // 輸出2

觀察是否有效

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);

if (wp.expired()) {
	cout << "sp 已經釋放,無效" << endl;
} else {
	cout << "sp 有效" << endl;		// 輸出
}

lock()函式,返回一個shared_ptr智慧指標,獲取所監視的shared_ptr

weak_ptr<int> gw;
auto spt = gw.lock();
cout << *spt << endl;