1. 程式人生 > >std::shared_ptr 和 std::weak_ptr引用計數的迴圈引用問題

std::shared_ptr 和 std::weak_ptr引用計數的迴圈引用問題

shared維護了一個指向control block的指標,control block內部包含了智慧指標物件的引用個數。

weak_ptr 是一種不控制物件生命週期的智慧指標, 它指向一個 shared_ptr 管理的物件. 進行該物件的記憶體管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理物件的一個訪問手段. 

  weak_ptr 設計的目的是為配合 shared_ptr 而引入的一種智慧指標來協助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 物件構造, 它的構造和析構不會引起引用記數的增加或減少. 
  定義在 memory 檔案中(非memory.h), 名稱空間為 std.

  weak_ptr 使用:

std::shared_ptr<int> sp(new int(10));
std::weak_ptr<int> wp(sp);
wp = sp;
printf("%d\n", wp.use_count()); // 1
wp.reset();
printf("%d\n", wp); // 0

// 檢查 weak_ptr 內部物件的合法性.
if (std::shared_ptr<int> sp = wp.lock())
{
}

成員函式

weak_ptr 沒有過載*和->但可以使用 lock 獲得一個可用的 shared_ptr 物件. 注意, weak_ptr 在使用前需要檢查合法性.

expired 用於檢測所管理的物件是否已經釋放, 如果已經釋放, 返回 true; 否則返回 false.
lock 用於獲取所管理的物件的強引用(shared_ptr). 如果 expired 為 true, 返回一個空的 shared_ptr; 否則返回一個 shared_ptr, 其內部物件指向與 weak_ptr 相同.
use_count 返回與 shared_ptr 共享的物件的引用計數.
reset 將 weak_ptr 置空.
weak_ptr 支援拷貝或賦值, 但不會影響對應的 shared_ptr 內部物件的計數.

如何判斷weak_ptr的物件是否失效?

應該使用的函式是lock(),lock會返回shared指標,判斷該指標是否為空。use_count()也可以得到shared引用的個數,但是速度較慢。

使用 weak_ptr 解決 shared_ptr 因迴圈引有不能釋放資源的問題

使用 shared_ptr 時, shared_ptr 為強引用, 如果存在迴圈引用, 將導致記憶體洩露. 而 weak_ptr 為弱引用, 可以避免此問題, 其原理:
  對於弱引用來說, 當引用的物件活著的時候弱引用不一定存在. 僅僅是當它存在的時候的一個引用, 弱引用並不修改該物件的引用計數, 這意味這弱引用它並不對物件的記憶體進行管理.
  weak_ptr 在功能上類似於普通指標, 然而一個比較大的區別是, 弱引用能檢測到所管理的物件是否已經被釋放, 從而避免訪問非法記憶體。

注意: 雖然通過弱引用指標可以有效的解除迴圈引用, 但這種方式必須在程式設計師能預見會出現迴圈引用的情況下才能使用, 也可以是說這個僅僅是一種編譯期的解決方案, 如果程式在執行過程中出現了迴圈引用, 還是會造成記憶體洩漏.

#include <iostream>
#include <memory>
 
class Woman;
class Man{
private:
	std::weak_ptr<Woman> _wife;
	//std::shared_ptr<Woman> _wife;
public:
	void setWife(std::shared_ptr<Woman> woman){
		_wife = woman;
	}
 
	void doSomthing(){
		if(_wife.lock()){
		}
	}
 
	~Man(){
		std::cout << "kill man\n";
	}
};
 
class Woman{
private:
	//std::weak_ptr<Man> _husband;
	std::shared_ptr<Man> _husband;
public:
	void setHusband(std::shared_ptr<Man> man){
		_husband = man;
	}
	~Woman(){
		std::cout <<"kill woman\n";
	}
};
 
 
int main(int argc, char** argv){
	std::shared_ptr<Man> m(new Man());
	std::shared_ptr<Woman> w(new Woman());
	if(m && w) {
		m->setWife(w);
		w->setHusband(m);
	}
	return 0;
}
在Man類內部會引用一個Woman,Woman類內部也引用一個Man。當一個man和一個woman是夫妻的時候,他們直接就存在了相互引用問題。man內部有個用於管理wife生命期的shared_ptr變數,也就是說wife必定是在husband去世之後才能去世。同樣的,woman內部也有一個管理husband生命期的shared_ptr變數,也就是說husband必須在wife去世之後才能去世。這就是迴圈引用存在的問題:husband的生命期由wife的生命期決定,wife的生命期由husband的生命期決定,最後兩人都死不掉,違反了自然規律,導致了記憶體洩漏。

     解決std::shared_ptr迴圈引用問題的鑰匙在weak_ptr手上。weak_ptr物件引用資源時不會增加引用計數,但是它能夠通過lock()方法來判斷它所管理的資源是否被釋放。另外很自然地一個問題是:既然weak_ptr不增加資源的引用計數,那麼在使用weak_ptr物件的時候,資源被突然釋放了怎麼辦呢?呵呵,答案是你根本不能直接通過weak_ptr來訪問資源。那麼如何通過weak_ptr來間接訪問資源呢?答案是:在需要訪問資源的時候weak_ptr為你生成一個shared_ptr,shared_ptr能夠保證在shared_ptr沒有被釋放之前,其所管理的資源是不會被釋放的。建立shared_ptr的方法就是lock()方法。

    細節:shared_ptr實現了operator bool() const方法來判斷一個管理的資源是否被釋放。