首頁 > 軟體

一起聊聊C++中的智慧指標

2022-07-18 14:05:20

一:背景

我們知道 C++ 是手工管理記憶體的分配和釋放,對應的操作符就是 new/delete 和 new[] / delete[], 這給了程式設計師極大的自由度也給了我們極高的門檻,弄不好就得記憶體洩露,比如下面的程式碼:

void test() {
	int* i = new int(10);
	*i = 10;
}

int main() {
	test();
}

這段程式碼因為用了 new 而忘了 delete,導致在 nt heap 上分配的 i 隨著棧地址的回收而成了一塊孤懸海外的記憶體佔用,所以修正後的程式碼如下:

void test() {
	int* i = new int(10);
	*i = 10;

	delete i;
}

int main() {
	test();
}

但這種寫法比較麻煩,智者千慮必有一失,總會有忘記加 delete 的時候,那怎麼辦呢? 大家應該知道記憶體自動管理有兩種手段。

1.參照計數

代表作有 Python,PHP,還有 windows 的控制程式碼管理。

2.參照跟蹤

代表作有 C#,JAVA 等一眾工程化語言。

因為 參照計數 實現比較簡單,主要就是記錄下物件的參照次數,次數為 0 則釋放,所以可完全藉助 類別建構函式解構函式 和 棧的自動回收特性 弄一個簡單的 參照計數 ,對應著如下四個關鍵詞。

  • auto_ptr
  • shared_ptr
  • unique_ptr
  • weak_ptr

接下來我們逐個聊一聊。

二:關鍵詞解析

1. auto_ptr

這是 C++ 最早出現一個的 簡單參照計數法,參考程式碼如下:

void test() {
	auto_ptr<int> ptr = auto_ptr<int>(new int(10));
}

int main() {
	test();
}

接下來看下組合程式碼:

    auto_ptr<int> ptr = auto_ptr<int>(new int(10));
...
00771D26  call        std::auto_ptr<int>::auto_ptr<int> (07710FAh)  
00771D2B  lea         ecx,[ebp-0D8h]  
00771D31  call        std::auto_ptr<int>::~auto_ptr<int> (0771159h) 

可以看到,它分別呼叫了 建構函式 和 解構函式,接下來找下 auto_ptr 這兩個函數的原始碼。

class auto_ptr {

private:
	_Ty* _Myptr; // the wrapped object pointer

public:
	auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept {
		_Ty* _Ptr = _Right._Ref;
		_Right._Ref = nullptr; // release old
		_Myptr = _Ptr; // reset this
	}

	~auto_ptr() noexcept {
		delete _Myptr;
	}
}

原始碼一看就明白了,在建構函式中,將 new int 的地址塞給了內部的 _Myptr 指標,在解構函式中對 _Myptr 進行 delete ,真好,這樣就不用整天擔心有沒有加 delete 啦。

值得注意的是,現在 C++ 不推薦這個了,而是建議使用新增的:shared_ptr,unique_ptr,weak_ptr, 怎麼說呢? auto_ptr 有一個不好處理的問題,就是現實開發中會出現這麼個場景,多個 ptr 指向同一個 參照,如下圖:

2. auto_ptr 多參照問題

方式1:

定義三個 ptr,然後包裝同一個 new int 地址,參考程式碼如下:

void test() {
	int* i = new int(10);

	auto_ptr<int> ptr1(i);
	auto_ptr<int> ptr2(i);
	auto_ptr<int> ptr3(i);
}

這種寫法有沒有問題呢? 肯定有問題啦,還記得 auto_ptr 的解構是 delete 嗎? 對同一塊記憶體多次 delete 會拋異常的,如下圖所示:

方式2:

既然定義三個有問題, 那就用賦值運運算元= 讓 ptr1,ptr2,ptr3 指向同一個地址是不是就可以啦? 參考程式碼如下:

void test() {
	int* i = new int(10);

	auto_ptr<int> ptr1(i);
	auto_ptr<int> ptr2 = ptr1;
	auto_ptr<int> ptr3 = ptr2;
}

int main() {
	test();
}

那這段程式碼有沒有問題呢? 有沒有問題得要看 = 運運算元是如何重寫的,扒一下原始碼看看。

template <class _Other>
auto_ptr& operator=(auto_ptr<_Other>& _Right) noexcept {
	reset(_Right.release());
	return *this;
}
_Ty* release() noexcept {
	_Ty* _Tmp = _Myptr;
	_Myptr = nullptr;
	return _Tmp;
}

從原始碼看有一個很噁心的點,他會將 _Right 下的 _Myptr 設為 nullptr,也就是說此時的 ptr1 報廢了,言外之意就是後續再存取 ptr1 會拋 存取違例。

哈哈,C++裡面的專業術語叫 控制權轉移。

以上就是一起聊聊C++中的智慧指標的詳細內容,更多關於C++智慧指標的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com