智能指針和異常

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

創(chuàng)新互聯(lián)企業(yè)建站,十余年網(wǎng)站建設(shè)經(jīng)驗(yàn),專注于網(wǎng)站建設(shè)技術(shù),精于網(wǎng)頁(yè)設(shè)計(jì),有多年建站和網(wǎng)站代運(yùn)營(yíng)經(jīng)驗(yàn),設(shè)計(jì)師為客戶打造網(wǎng)絡(luò)企業(yè)風(fēng)格,提供周到的建站售前咨詢和貼心的售后服務(wù)。對(duì)于網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)中不同領(lǐng)域進(jìn)行深入了解和探索,創(chuàng)新互聯(lián)在網(wǎng)站建設(shè)中充分了解客戶行業(yè)的需求,以靈動(dòng)的思維在網(wǎng)頁(yè)中充分展現(xiàn),通過對(duì)客戶行業(yè)精準(zhǔn)市場(chǎng)調(diào)研,為客戶提供的解決方案。

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

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

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

在實(shí)際寫代碼過程中,我們很容易寫出存在異常的代碼,不信來看看下面幾個(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++采用了下面的代碼來捕獲異常:

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

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

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

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

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

智能指針和異常

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

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

//現(xiàn)代寫法
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;
};

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

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

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

但對(duì)于現(xiàn)代寫法,拷貝后將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è)重要的問題:

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

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

2,循環(huán)引用

我們用一個(gè)簡(jiǎn)單的例子來說明一下什么是循環(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),咦,為什么沒有輸出呢?也就是說cur和next根本沒用被釋放。大家看下面的圖

智能指針和異常

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

因?yàn)閏ur->_next和next->_prev相互依賴,所以在出了作用域以后都等著對(duì)方釋放,處于一種僵持狀態(tà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)引用的問題。

3,定置刪除器

先介紹一個(gè)概念仿函數(shù),顧名思義它類似于函數(shù)但并不是函數(shù),而是一個(gè)類。只不過在類的對(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);
}

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

(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ì)崩潰。

怎么解決上面的問題的?

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í)例。

那接下來就用它來模擬實(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)));
}

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

本文標(biāo)題:智能指針和異常
本文地址:http://muchs.cn/article0/gdedoo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、關(guān)鍵詞優(yōu)化面包屑導(dǎo)航、搜索引擎優(yōu)化網(wǎng)頁(yè)設(shè)計(jì)公司、網(wǎng)站收錄

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站托管運(yùn)營(yíng)