溫馨提示×

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

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

智能指針和異常

發(fā)布時(shí)間:2020-07-28 15:24:19 來(lái)源:網(wǎng)絡(luò) 閱讀:520 作者:賈珍珍 欄目:編程語(yǔ)言

今天讓我們來(lái)分析一下C++中的智能指針和異常,首先呢先普及一下概念!

(1)智能指針:智能或者自動(dòng)化的管理指針?biāo)鶗?huì)向的動(dòng)態(tài)資源的釋放。

(2)異常:當(dāng)一個(gè)函數(shù)發(fā)現(xiàn)自己無(wú)法處理的錯(cuò)誤時(shí),讓函數(shù)的調(diào)用者直接或間接的處理這個(gè)問(wèn)題。

(3)RAII:資源分配即初始化。構(gòu)造函數(shù)完成對(duì)象的初始化,析構(gòu)函數(shù)完成對(duì)象的清理,而不是刪除。

在實(shí)際寫(xiě)代碼過(guò)程中,我們很容易寫(xiě)出存在異常的代碼,不信來(lái)看看下面幾個(gè)例子 :

void Test()
{
	int *p = new int(1);
	if (1)
	{
		return;
	}
	delete p;
}

很容易可以看出在if語(yǔ)句中已經(jīng)返回了,那后面的代碼自然是執(zhí)行不了了,所以就出現(xiàn)了內(nèi)存泄露的危險(xiǎn),這可是非??膳碌哪?,它可能會(huì)耗盡內(nèi)存,不僅當(dāng)前程序會(huì)崩潰,嚴(yán)重的整個(gè)系統(tǒng)都會(huì)崩潰,這是看你怎么辦,哈哈。這時(shí)肯定會(huì)有人想到了C++里面不是有異常捕獲嗎?是的,為了增加代碼的兼容性,C++采用了下面的代碼來(lái)捕獲異常:

throw 拋出異常;
try
{
	//可能發(fā)生異常的代碼
}
catch (異常類型)
{
	//發(fā)生異常后的處理方法
}

上面的代碼進(jìn)行這樣處理不就沒(méi)事了嗎?

void Test()
{
	int *p = new int(1);
	try
	{
		if (1)
		{
			throw 1;
		}
	}
	catch (int e)
	{
		delete p;
		throw;
	}
	delete p;
}

但是這里在catch中卻二次拋出異常,這樣管理起來(lái)非?;靵y。所以就引入了智能指針,用它來(lái)解決異常更方便。上面提到的RAII就是編寫(xiě)異常安全代碼的關(guān)鍵思想。

下來(lái)介紹一下Boost庫(kù)里的智能指針吧。

智能指針和異常

下面給出這些智能指針的模擬實(shí)現(xiàn)接口,具體的實(shí)現(xiàn)自己完成嘍

(1)AutoPtr有兩種實(shí)現(xiàn)方法

//現(xiàn)代寫(xiě)法
template <typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr);
	AutoPtr(AutoPtr<T>& ap);
	AutoPtr<T>& operator=(AutoPtr<T>& ap);
	T& operator*();
	T* operator->();
	~AutoPtr();
protected:
	T* _ptr;
};
//舊方法
template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr);
	AutoPtr(AutoPtr<T>& ap);
	AutoPtr<T>& operator=(AutoPtr<T>& ap);
	T& operator*();
	T* operator->();
	~AutoPtr();
protected:
	T* _ptr;
	bool _owner;
};

很容易可以看出舊寫(xiě)法里多了一個(gè)布爾類型的成員變量,大家都知道AutoPtr采用的是管理權(quán)轉(zhuǎn)移的方式來(lái)管理的。所以誰(shuí)是資源的管理者,誰(shuí)的_owner值就為true,其他的對(duì)象的_owner自然就是false。在現(xiàn)代寫(xiě)法中去除了這種方式,直接采用對(duì)象賦空的方式,看起來(lái)還是舊的寫(xiě)法似乎更為合理一點(diǎn),但是新事物的出現(xiàn)肯定有它存在的理由,下來(lái)我們看個(gè)例子:

AutoPtr<int> ap1(new int(1));
if (某個(gè)條件)
{
	AutoPtr<int> ap2(ap1);
}

大家想到了嗎?對(duì)于舊寫(xiě)法,如果進(jìn)入if語(yǔ)句,創(chuàng)建ap2后進(jìn)行拷貝ap1._owner=false,ap2._owner=true;但是出了if代碼塊,就出了ap2的作用域,ap2被釋放,但ap1里還保存著原來(lái)的地址,所以可能發(fā)生二次釋放或訪問(wèn)問(wèn)題,但這個(gè)時(shí)候ap1就是野指針啦,后果很?chē)?yán)重呢!

但對(duì)于現(xiàn)代寫(xiě)法,拷貝后將ap1賦空,想要再用ap1時(shí)進(jìn)行檢測(cè)為空,那也就避免使用它啦。

(2)ScopedPtr

template<typename T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr);
	~ScopedPtr();
	T& operator*();
	T* operator->();
protected:
	ScopedPtr(ScopedPtr<T>& sp);
	ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
protected:
	T* _ptr;
};

容易看出它里面直接把拷貝構(gòu)造函數(shù)和重載賦值函數(shù)聲明為了保護(hù)或者私有的,在類外是不能被調(diào)用的,也就起到了防拷貝的作用。

(3)SharedPtr

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* ptr);
	SharedPtr(const SharedPtr<T>& sp);
	~SharedPtr();
	SharedPtr<T>& operator=(const SharedPtr<T>& sp);
	T& operator*();
	T* operator->();
public:
	void UseCount();
	void GetPtr();
protected:
	void _Release();
protected:
	T* _ptr;
	long* _pCount;
};

(4)ScopedArray/SharedArray

template<typename T>
class ScopedArray
{
public:
	ScopedArray(T* ptr);
	~ScopedArray();
	T& operator[](size_t index);
protected:
	ScopedArray(const ScopedArray<T>& sp);
	ScopedArray<T>& operator=(const ScopedArray<T>& sp);
protected:
	T* _ptr;
};

//
template<typename T>
class SharedArray
{
public:
	SharedArray(T* ptr);
	~SharedArray();
	SharedArray(const SharedArray<T>& sp);
	SharedArray<T>& operator=(const SharedArray<T>& sp);
	T& operator[](size_t index);
protected:
	void _Release();
protected:
	T* _ptr;
	long* _pCount;
};

這些當(dāng)中SharedPtr是使用最廣泛的,但在上面SharedPtr的實(shí)現(xiàn)方法中還存在幾個(gè)重要的問(wèn)題:

1,引用計(jì)數(shù)器更新存在著線程安全

對(duì)于這個(gè)問(wèn)題呢,我還沒(méi)有能力去解決,待我學(xué)成歸來(lái),一定收拾它。

2,循環(huán)引用

我們用一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明一下什么是循環(huán)引用

#include<memory>
struct Node//定義一個(gè)雙向鏈表
{
	shared_ptr<Node> _next;
	shared_ptr<Node> _prev;
	~Node()
	{
		cout << "delete" << this << endl;
	}
};
void TestSharedPtr()
{
	shared_ptr<Node> cur(new Node());
	shared_ptr<Node> next(new Node())
	cur->_next = next;
	next->_prev = cur;

}

運(yùn)行這段代碼你會(huì)發(fā)現(xiàn),咦,為什么沒(méi)有輸出呢?也就是說(shuō)cur和next根本沒(méi)用被釋放。大家看下面的圖

智能指針和異常

在用SharedPtr實(shí)現(xiàn)雙向鏈表時(shí),會(huì)發(fā)生循環(huán)引用,這是一個(gè)很特殊的場(chǎng)景。

因?yàn)閏ur->_next和next->_prev相互依賴,所以在出了作用域以后都等著對(duì)方釋放,處于一種僵持狀態(tài)。因而沒(méi)用調(diào)用析構(gòu)函數(shù)。

解決方法是采用weak_ptr弱指針,如下:

#include<memory>
struct Node//定義一個(gè)雙向鏈表
{
	weak_ptr<Node> _next;
	weak_ptr<Node> _prev;
	~Node()
	{
		cout << "delete" << this << endl;
	}
};

weak_ptr在創(chuàng)建對(duì)象時(shí)引用計(jì)數(shù)為1,而在cur->_next=next和next->_prev=cur中對(duì)cur和next的引用計(jì)數(shù)不加1,這就很好的解決了循環(huán)引用的問(wèn)題。

3,定置刪除器

先介紹一個(gè)概念仿函數(shù),顧名思義它類似于函數(shù)但并不是函數(shù),而是一個(gè)類。只不過(guò)在類的對(duì)象調(diào)用成員函數(shù)的時(shí)候特別像是函數(shù)的調(diào)用,也就是在 這個(gè)類中重載了operator()??磧蓚€(gè)小例子:

(1)

void TestSharedPtr()
{
	int *p1 = (int *)malloc(sizeof(int)* 10);
	shared_ptr<int> sp1(p1);
}

程序看上去沒(méi)有問(wèn)題,可能也沒(méi)有崩潰,可是malloc和free要成對(duì)使用,這里存在內(nèi)存泄露的問(wèn)題。

(2)

void TestSharedPtr()
{
	FILE* p2 = fopen("test.cpp", "r");
	shared_ptr<FILE> sp2(p2,Fclose());//出作用域釋放文件,發(fā)生崩潰
}

當(dāng)shared_ptr對(duì)象是文件時(shí),出作用域釋放,系統(tǒng)自然會(huì)崩潰。

怎么解決上面的問(wèn)題的?

struct Free
{
	void operator()(void* ptr)
	{
		cout << "Free:" << ptr << endl;
		free(ptr);
	}
};
struct Fclose
{
	void operator()(void* ptr)
	{
		cout << "Fclose" << ptr << endl;
		fclose((FILE*)ptr);
	}
};
void TestSharedPtr()
{
	int *p1 = (int *)malloc(sizeof(int)* 10);
	shared_ptr<int> sp1(p1,Free());
	Fclose f;
	FILE* p2 = fopen("test.cpp", "r");
	shared_ptr<FILE> sp2(p2,Fclose());
}

這就是一個(gè)仿函數(shù)的使用實(shí)例。

那接下來(lái)就用它來(lái)模擬實(shí)現(xiàn)shared_ptr的定置刪除器吧,同樣還是給出接口。

template<typename T>
struct DefaultDel
{
	void operator()(T* ptr)
	{
		delete ptr;
	}
};
template<typename T>
struct Free
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};
template<typename T,typename D=DefaultDel<T>>
class SharedPtr
{
public:
	SharedPtr(T* ptr);
	SharedPtr(T* ptr,D del);
	SharedPtr(const SharedPtr<T,D>& sp);
	~SharedPtr();
	SharedPtr<T,D>& operator=(const SharedPtr<T,D>& sp);
	T& operator*();
	T* operator->();
public:
	void UseCount();
	void GetPtr();
protected:
	void _Release();
protected:
	T* _ptr;
	long* _pCount;
	D _del;
};
void TestDeleter()
{
	SharedPtr<int, DefaultDel<int>> sp1(new int(1));
	SharedPtr<int, Free<int>> sp2((int *)malloc(sizeof(int)));
	SharedPtr<int> sp3(new int(1));
	SharedPtr<int, Free<int>> sp4((int *)malloc(sizeof(int)));
}

好了,智能指針就說(shuō)這么多啦。小伙伴明白了嗎?

向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