溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

淺談RAII&智能指針

發(fā)布時(shí)間:2020-07-31 14:24:22 來(lái)源:網(wǎng)絡(luò) 閱讀:454 作者:崔雨軒 欄目:編程語(yǔ)言

  關(guān)于RAII,官方給出的解釋是這樣的“資源獲取就是初始化”。聽(tīng)起來(lái)貌似不是很懂的哈,其實(shí)說(shuō)的通俗點(diǎn)的話就是它是一種管理資源,避免內(nèi)存泄漏的一種方法。它可以保證在各種情況下,當(dāng)你對(duì)對(duì)象進(jìn)行使用時(shí)先通過(guò)構(gòu)造函數(shù)來(lái)進(jìn)行資源的分配和初始化,最后通過(guò)析構(gòu)函數(shù)來(lái)進(jìn)行清理,有效的保證了資源的正確分配和釋放。(特別是在異常中,因?yàn)楫惓M鶗?huì)改變代碼正確的執(zhí)行順序,這就很容易引起資源管理的混亂和內(nèi)存的泄漏)

  其中智能指針就是RAII的一種實(shí)現(xiàn)模式,所謂的智能就是它可以自動(dòng)化的來(lái)管理它所指向那份空間的資源分配和釋放。下面先介紹一下庫(kù)中的智能指針吧:

這是Boost庫(kù)中的智能指針:

淺談RAII&智能指針

而在STL中之前是只有auto_ptr的,但在C++11標(biāo)準(zhǔn)中也引入了unique_ptr/shared_ptr/weak_ptr。(ps:unique_ptr就是Boost中的scoped_ptr)

  接下來(lái)我就來(lái)好好的,仔細(xì)地介紹介紹它們哈:

1.auto_ptr(管理權(quán)的轉(zhuǎn)移)

 很多人看書(shū)和資料上面說(shuō)auto_ptr是一種變性類(lèi)型的RAII,其實(shí)這里所說(shuō)的變性實(shí)際上是一種管理權(quán)轉(zhuǎn)移特質(zhì),auto_ptr實(shí)際上就是通過(guò)這一特質(zhì)來(lái)實(shí)現(xiàn)資源的管理和釋放的,這就好比說(shuō)一扇門(mén)只有一把鑰匙,拿鑰匙的人擁有開(kāi)這扇門(mén)的權(quán)利,而當(dāng)另一個(gè)人從這個(gè)人這兒把鑰匙拿走后,他開(kāi)門(mén)的權(quán)利也轉(zhuǎn)到另一個(gè)人那了,因?yàn)殍€匙被拿走了。

下面是一個(gè)簡(jiǎn)單的auto_ptr的實(shí)現(xiàn),它能很好的證明上面的例子:

template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr=NULL)
		:_ptr(ptr)
	{}
	AutoPtr(AutoPtr<T>& a)
		:_ptr(a._ptr)
	{
		a._ptr = NULL;
	}
	AutoPtr<T>& operator=(AutoPtr<T>& a)
	{
		_ptr = a._ptr;
		a._ptr = NULL;
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~AutoPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
		}
	}
protected:
	T* _ptr;
};

當(dāng)發(fā)生賦值運(yùn)算和拷貝構(gòu)造時(shí),之前的指針在賦值過(guò)后就被置成空了,也就是說(shuō)真正能夠訪問(wèn)內(nèi)存的只有當(dāng)前的指針。當(dāng)然這種方法也使它的局限性很高,因?yàn)橹暗闹羔槦o(wú)法對(duì)再訪問(wèn)該區(qū)域,這使得它的實(shí)用性并不強(qiáng),之所以保留它主要還是為了維護(hù)之前的一些程序。

2.scoped_ptr(簡(jiǎn)單粗暴的獨(dú)裁者)

  首先我們先來(lái)看下它的簡(jiǎn)單實(shí)現(xiàn)吧:

template<typename T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr = NULL)
		:_ptr(ptr)
	{}
	~ScopedPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
protected:
	ScopedPtr(const ScopedPtr<T>& s);
	ScopedPtr<T>& operator=(const ScopedPtr<T>& s);
protected:
	T* _ptr;
};

其實(shí)從代碼中我們能很容易看出它的簡(jiǎn)單粗暴了,它就根本不允許你對(duì)它進(jìn)行拷貝構(gòu)造和賦值,它將賦值重載和拷貝構(gòu)造兩個(gè)函數(shù)只進(jìn)行了聲明而沒(méi)有實(shí)現(xiàn),這樣它就強(qiáng)制限定你不可能在使用其他指針訪問(wèn)這塊空間,所以說(shuō)說(shuō)它是個(gè)獨(dú)裁者一點(diǎn)也不為過(guò),當(dāng)然這種指針一般是在特殊的場(chǎng)合出現(xiàn),并不常用,因?yàn)樗拗屏酥羔樀囊粋€(gè)很重要的特點(diǎn):靈活性!

3.shared_ptr(計(jì)數(shù)器原理應(yīng)用)

  shared_ptr是比較流行和實(shí)用的智能指針了,它通過(guò)計(jì)數(shù)器原理解決了上述兩種智能指針訪問(wèn)唯一性的問(wèn)題,它允許多個(gè)指針訪問(wèn)同一塊空間,并且在析構(gòu)時(shí)也能夠保證內(nèi)存正確釋放。那它是怎樣一種機(jī)制呢?且看下面的代碼:

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* ptr=NULL)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	SharedPtr(SharedPtr<T>& s)
		:_ptr(s._ptr)
		, _pcount(s._pcount)
	{
		
		++(*_pcount);
	}
	SharedPtr<T>& operator=(SharedPtr<T> s)
	{
		swap(_ptr, s._ptr);
		swap(_pcount, s._pcount);
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	~SharedPtr()
	{
		Reservs();
	}
public:
	void Reservs()
	{
		if (--(*_pcount) == 0)
		{
			delete _ptr;
			delete _pcount;
		}
	}
protected:
	T* _ptr;
	int* _pcount;
};

首先它在類(lèi)模版中的成員變量增加了計(jì)數(shù)指針用來(lái)統(tǒng)計(jì)該內(nèi)存目前被多少指針管理,然后凡是有拷貝和賦值的統(tǒng)統(tǒng)在計(jì)數(shù)器上進(jìn)行累加,而在析構(gòu)的時(shí)候只需要檢查計(jì)數(shù)器內(nèi)當(dāng)前的計(jì)數(shù)是否唯1,不唯1的話說(shuō)明當(dāng)前還有多個(gè)指針在使用它,那此時(shí)我們并不釋放它,只將它的計(jì)數(shù)減1就好;如果析構(gòu)時(shí)它的計(jì)數(shù)到1了,那就說(shuō)明當(dāng)前只有一個(gè)指針在維護(hù)它,這時(shí)候再去釋放該內(nèi)存就變得很合理了。這就是shared_ptr整個(gè)實(shí)現(xiàn)過(guò)程和實(shí)現(xiàn)原理。

4.scoped_array和shared_ptr

  關(guān)于scoped_array和shared_array,它們和scoped_ptr和shared_ptr其實(shí)大同小異,它們的實(shí)現(xiàn)原理都是一樣的,只不過(guò)一個(gè)是用new[]和delete[]的,一個(gè)是用new和delete的。本質(zhì)上他們是沒(méi)有任何區(qū)別的,通過(guò)下面的代碼我們能夠很直觀看出來(lái):

scoped_array:

template<typename T>
class ScopedArry
{
public:
	ScopedArry(T* ptr = NULL)
		:_ptr(ptr)
	{}
	~ScopedArry()
	{
		if (_ptr != NULL)
		{
			delete[] _ptr;
		}
	}
	T& operator[](int index)
	{
		return _ptr[index];
	}
protected:
	ScopedArry(const ScopedPtr<T>& s);
	ScopedArry<T>& operator=(const ScopedArry<T>& s);
protected:
	T* _ptr;
};


shared_array:

template<typename T>
class SharedArry
{
public:
	SharedArry(T* ptr = NULL)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	SharedArry(SharedArry<T>& s)
		:_ptr(s._ptr)
		, _pcount(s._pcount)
	{

		++(*_pcount);
	}
	SharedArry<T>& operator=(SharedArry<T> s)
	{
		swap(_ptr, s._ptr);
		swap(_pcount, s._pcount);
		return *this;
	}
	T& operator[](int index)
	{
		return _ptr[index];
	}
	~SharedArry()
	{
		if (--(*_pcount) == 0)
		{
			delete[] _ptr;
		}
	}
protected:
	T* _ptr;
	int* _pcount;
};

整體而言數(shù)組我們只用重載[]就可以對(duì)其元素進(jìn)行訪問(wèn),并不用重載*和&來(lái)訪問(wèn)它們了,這比指針相對(duì)而言能方便點(diǎn)。

5.weak_ptr(輔助shared_ptr)

  上面介紹了shared_ptr,在這里要說(shuō)明一點(diǎn)的是我上面的代碼并不是庫(kù)中的標(biāo)準(zhǔn)代碼,只是造了幾個(gè)輪子,這是為了方便向大家講解它們的實(shí)現(xiàn)原理和運(yùn)行機(jī)制,其實(shí)真正庫(kù)里的代碼實(shí)現(xiàn)是很復(fù)雜的,下面我們可以看看boost庫(kù)中shared_ptr和weak_ptr的框架類(lèi)圖:

淺談RAII&智能指針


其實(shí)通過(guò)這張圖我們可以看出智能指針的實(shí)現(xiàn)要比我們想象的復(fù)雜,但是它們實(shí)現(xiàn)的原理和我們介紹的是一樣一樣的,感興趣的同學(xué)可以去庫(kù)里面研究研究,博主就不一一的發(fā)出來(lái)了。

 OK,我們?cè)倩氐秸}上來(lái),為什么說(shuō)weak_ptr是輔助shared_ptr的呢?其實(shí)在真正的運(yùn)用中我們還會(huì)發(fā)現(xiàn)shared_ptr還有些不足之處,它有時(shí)并不能很好完成一些任務(wù),并且還會(huì)出現(xiàn)一些問(wèn)題,其中和weak_ptr有關(guān)的一個(gè)問(wèn)題就是——循環(huán)引用。

那循環(huán)引用是怎么造成的呢?請(qǐng)看下圖:

淺談RAII&智能指針

再來(lái)個(gè)代碼吧:

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
using namespace boost;
struct ListNode
{
shared_ptr<ListNode > _prev;
shared_ptr<ListNode > _next;
//weak_ptr<ListNode > _prev;
//weak_ptr<ListNode > _next;
~ ListNode()
{
cout<<"~ListNode()" <<endl;
}
};
void Test ()
{
// 循環(huán)引用問(wèn)題
shared_ptr <ListNode > p1( new ListNode ());
shared_ptr <ListNode > p2( new ListNode ());
cout <<"p1->Count:" << p1. use_count()<<endl ;
cout <<"p2->Count:" << p2. use_count()<<endl ;
// p1節(jié)點(diǎn)的_next指向 p2節(jié)點(diǎn)
p1->_next = p2;
// p2節(jié)點(diǎn)的_prev指向 p1節(jié)點(diǎn)
p2->_prev = p1;
cout <<"p1->Count:" << p1. use_count ()<<endl ;
cout <<"p2->Count:" << p2. use_count ()<<endl ;
}

當(dāng)我們用shared_ptr創(chuàng)建兩個(gè)雙向結(jié)點(diǎn)時(shí),并將它們連接起來(lái)后就會(huì)出現(xiàn)問(wèn)題,試想當(dāng)你用p1的_next指向p2時(shí),它的引用計(jì)數(shù)會(huì)加1,同樣p2的_prev指向p1時(shí)也會(huì)使p1的引用計(jì)數(shù)增加,這就會(huì)出現(xiàn)一個(gè)問(wèn)題——當(dāng)你釋放的時(shí)候,p2是要先釋放的,對(duì)吧?可是p2在釋放時(shí)并沒(méi)法將其指向的空間釋放掉,因?yàn)樗挠?jì)數(shù)是2,它只會(huì)將計(jì)數(shù)器減1,而真正要釋放那塊空間的是p1_next,同樣當(dāng)p1進(jìn)行釋放時(shí)也只是計(jì)數(shù)器減1,它所指向的那塊空間也沒(méi)有被釋放,真正釋放那塊空間的其實(shí)是p2_prev,這時(shí)就導(dǎo)致了一個(gè)問(wèn)題,就是兩邊都在等著對(duì)方先釋放,因此陷入無(wú)限的循環(huán)當(dāng)中。

  這就是循環(huán)引用的出現(xiàn)的原因,從中我們可以清楚找到問(wèn)題所在,就是在創(chuàng)建_next和_prev時(shí)使得其引用計(jì)數(shù)進(jìn)行了累加,因此為了解決此類(lèi)問(wèn)題我們引入了weak_ptr,它就是用來(lái)解決循環(huán)引用問(wèn)題的,使用weak_ptr類(lèi)型的指針并不會(huì)使shared_ptr的引用計(jì)數(shù)加1,這也就不會(huì)產(chǎn)生循環(huán)引用的問(wèn)題了。下面可以通過(guò)上述代碼的運(yùn)行結(jié)果直觀的看到weak_ptr實(shí)現(xiàn)機(jī)制:

使用shared_ptr:

淺談RAII&智能指針

使用weak_ptr:

淺談RAII&智能指針

這其實(shí)也是weak_ptr存在的意義,輔助shared_ptr,使得它們用起來(lái)跟我們使用平常的指針一模一樣,并且還非常方便,不用我們?nèi)タ紤]內(nèi)存的釋放和泄漏的問(wèn)題。

  好了,由于博主水平并不是很高,只能向大家解釋這么多了,有要補(bǔ)刀或有問(wèn)題的大神請(qǐng)?jiān)谙路搅粞怨?/p>

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI