溫馨提示×

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

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

c++智能指針的不斷演化

發(fā)布時(shí)間:2020-06-24 10:47:23 來源:網(wǎng)絡(luò) 閱讀:995 作者:羌笛夜 欄目:編程語言

RAII資源分配即初始化,定義一個(gè)類來封裝資源的分配和釋放,在構(gòu)造 函數(shù)完成資源的分配和初始化,在析構(gòu)函數(shù)完成資源的清理,可以保證資源的正確初始化和釋放。 

智能指針的引入:

由于return ,throw等關(guān)鍵字的存在,導(dǎo)致順序執(zhí)行流的錯(cuò)亂,不斷的進(jìn)行跳轉(zhuǎn),使開辟的空間

看似被釋放,而實(shí)際上導(dǎo)致內(nèi)存的泄露。

例如以下兩個(gè)例子:

void Test1()
{
	int *p1 = new int(1);
	if (1)
	{
		return;
	}
	delete p1;
}

void DoSomeThing() 
{     //...     
	throw 2 ;
	//... 
} 
void Test2 () 
{     
	int* p1 = new int(2);   
	//...    
	try 
	{          
		DoSomeThing();  
	}     
	catch(...)    
	{          
		//delete p1 ;         
		throw;  
	}     
	//...     
	delete p1 ;
}

以上兩個(gè)例子,看似new與delete結(jié)合使用,但是實(shí)際上已經(jīng)導(dǎo)致內(nèi)存的泄露,為了解決以上問題,就需要在寫此類代碼時(shí)需要多加小心,但是對(duì)于大量的代碼開發(fā),想要注意就很難了。

于是乎就引入了智能指針:

所謂智能指針就是智能/自動(dòng)化的管理指針?biāo)赶虻膭?dòng)態(tài)資源的釋放。

c++庫函數(shù)中的智能指針為(auto_ptr,unique_ptr,shared_ptr);

Boost庫函數(shù)中的智能指針為(auto_ptr,scoped_ptr,shared_ptr)。


接下來就一起來了解一下智能指針的發(fā)展歷史。

1、在vc6.0之前的版本中auto_ptr的模擬實(shí)現(xiàn)方式

template<class T>
class OldAutoPtr
{
public:
	OldAutoPtr(T *ptr)
		:_ptr(ptr)
		, _owner(true)
	{}

	//拷貝構(gòu)造
	OldAutoPtr(OldAutoPtr& ap)
		:_ptr(ap._ptr)
		,_owner(ap._owner)
	{
		ap._owner = false;//權(quán)限的轉(zhuǎn)讓
	}

	//運(yùn)算符重載
	OldAutoPtr& operator=(OldAutoPtr& ap)
	{
		if (ap._ptr != _ptr)//自賦值和指向同一份空間
		{
			if (_ptr)
			{
				delete _ptr;
			}
			_ptr = ap._ptr;
			_owner = ap._owner;//權(quán)限的轉(zhuǎn)讓
			ap._owner = false;
		}
		return *this;
	}

	//析構(gòu)函數(shù)
	~OldAutoPtr()
	{
		if (_owner)//只刪除擁有權(quán)限的指針
		{
			delete _ptr;
		}
	}

public:

	T& operator *()
	{
		return *_ptr;
	}

	T* operator ->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	bool _owner;//權(quán)限擁有者
};


評(píng)價(jià):

看似最初版本的auto_ptr已經(jīng)很接近了原聲指針的使用,多個(gè)指針都可以指向一份空間,而且釋放的同時(shí)不會(huì)導(dǎo)致同一份空間的多次釋放。但是在下面的情境中卻發(fā)生了致命的危害:


void TestOldAutoPtr()
{
	OldAutoPtr<int> ap1(new int(1));
	if (true)
	{
		OldAutoPtr<int> ap2(ap1);
		//OldAutoPtr<int> ap3(new int(2));
		//ap3 = ap2;
	}
	//ap1將析構(gòu)的權(quán)限給予了ap2,ap1,ap2指向同一份空間,ap2在出了if作用域之后ap2對(duì)象釋放,進(jìn)而導(dǎo)致ap1也被釋放。
	//但是在if作用域之外,又對(duì)ap1(ap2)指向的空間進(jìn)行簡引用,導(dǎo)致程序崩潰,如果不使用的話則會(huì)造成指針的懸掛(野指針)。
	*ap1 = 10;
}

針對(duì)以上的狀況,在之后的版本中對(duì)auto_ptr進(jìn)行了完全的權(quán)限轉(zhuǎn)移,轉(zhuǎn)移之后該指針不能使用。


2、現(xiàn)在的版本auto_ptr的實(shí)現(xiàn)

template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = NULL;//權(quán)限轉(zhuǎn)移
	}

	AutoPtr<T>& operator=(AutoPtr<T> & ap)
	{
		if (this != &ap)//自賦值
		{
			if (_ptr)
			{
				delete _ptr;
			}
			_ptr = ap._ptr;
			ap._ptr = NULL;//權(quán)限轉(zhuǎn)移
		}
		return *this;
	}

	~AutoPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
			_ptr = NULL;
		}
	}
public:
	T& operator * ()//沒有參數(shù)
	{
		return *_ptr;
	}

	T* operator->()//沒有參數(shù)
	{
		return _ptr;
	}
private:
	T* _ptr;
};

評(píng)價(jià):

這種實(shí)現(xiàn)方式很好的解決了old的版本特殊場景的野指針問題,但是它與原生指針的差別更大,由于實(shí)現(xiàn)了完全的權(quán)限轉(zhuǎn)移,所以導(dǎo)致在拷貝構(gòu)造和賦值之后只有一個(gè)指針可以使用,而其他指針都置為NULL,使用很不方便,而且還很容易導(dǎo)致對(duì)于NULL指針的截引用,導(dǎo)致程序崩潰,其危害也是比較大的。


3、針對(duì)以上的問題,在Boost庫中引入了簡單粗暴的解決方法scoped_ptr,直接不允許其拷貝構(gòu)造和賦值(ps:新的C++11標(biāo)準(zhǔn)中叫做unique_ptr)

解決辦法:將賦值和拷貝構(gòu)造函數(shù)只聲明,不實(shí)現(xiàn),切記,將其聲明為protected或者private以免在類的外部對(duì)其進(jìn)行破環(huán)性處理,同時(shí)也是提高了代碼的安全性。

template<typename T>
class ScopedPtr
{
	
public:
	ScopedPtr(T* ptr)
		:_ptr(ptr)
	{}
	~ScopedPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
		}
	}
protected://將其聲明為protected或者private不能聲明為public
	ScopedPtr(ScopedPtr<T>& sp);
	ScopedPtr<T>operator=(ScopedPtr<T>&sp);
public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T *_ptr;
};

評(píng)價(jià):

在一般的情況下,如果不需要對(duì)于指針的內(nèi)容進(jìn)行拷貝,賦值操作,而只是為了防止內(nèi)存泄漏的發(fā)生,該只能指針完全可以滿足需求。


4、允許拷貝構(gòu)造和賦值的shared_ptr模擬實(shí)現(xiàn)

解決辦法:

通過為每個(gè)空間多開辟4個(gè)字節(jié)做為引用計(jì)數(shù)器,在拷貝構(gòu)造、賦值、析構(gòu)時(shí)用計(jì)數(shù)器來解決。

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pCount(new int(1))
	{}

	SharedPtr(const SharedPtr<T>& sp)
	{
		_ptr = sp._ptr;
		_pCount = sp._pCount;
		(*_pCount)++;
	}

	SharedPtr<T>operator=(SharedPtr<T> sp)
	{
		swap(_ptr, sp._ptr);
		swap(_pCount, sp._pCount);
		return *this;
	}

	~SharedPtr()
	{
		_Realse();
	}
public:
	void _Realse()
	{
		if (--(*_pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
		}
	}

public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pCount;
};

評(píng)價(jià):

簡化版智能指針SharedPtr看起來不錯(cuò),但是實(shí)際上存在以下問題: 

1)循環(huán)引用 

2)定置刪除器

 

循環(huán)引用

c++智能指針的不斷演化

template<class T>
struct Node
{
public:
	~Node()
	{
		cout << "delete:" << this << endl;
	}
public:
	shared_ptr<Node> _prev;
	shared_ptr<Node> _next;
	/*weak_ptr<Node> _prev;
	weak_ptr<Node> _next;*/
};

void test_round()//循環(huán)引用
{
	shared_ptr<Node> cur(new Node());
	shared_ptr<Node> next(new Node());

	cout << "連接前:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;

	cur->_next = next;
	next->_prev = cur;

	cout << "連接后:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;


	/*shared_ptr<Node> cur(new Node());
	weak_ptr<Node> wp1(cur);*/
}

c++智能指針的不斷演化

解決辦法:

使用一個(gè)弱引用智能指針(weak_ptr)來打破循環(huán)引用(weak_ptr不增加引用計(jì)數(shù))

template<class T>
struct Node
{
public:
	~Node()
	{
		cout << "delete:" << this << endl;
	}
public:
	//shared_ptr<Node> _prev;
	//shared_ptr<Node> _next;
	weak_ptr<Node> _prev;
	weak_ptr<Node> _next;
};

void test_round()//循環(huán)引用
{
	shared_ptr<Node> cur(new Node());
	shared_ptr<Node> next(new Node());

	cout << "連接前:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;

	cur->_next = next;
	next->_prev = cur;

	cout << "連接后:" << endl;
	cout << "cur:" << cur.use_count() << endl;
	cout << "next:" << next.use_count() << endl;


	/*shared_ptr<Node> cur(new Node());
	weak_ptr<Node> wp1(cur);*/
}

c++智能指針的不斷演化


定置刪除器

在shared_ptr中只能處理釋放new開辟的空間,而對(duì)于malloc,以及fopen打開的文件指針不能處理,為了能夠全面的處理各種各樣的指針,所以提出了定制刪除器,而其實(shí)現(xiàn)則是通過仿函數(shù)(通過對(duì)()運(yùn)算符的重載)來實(shí)現(xiàn)。

1)模擬實(shí)現(xiàn)的解決

template<typename T,typename D>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pCount(new int(1))
	{}

	SharedPtr(T* ptr, D del)
		:_ptr(ptr)
		, _pCount(new int(1))
		, _del(del)
	{}

	SharedPtr(const SharedPtr<T, D>& sp)
	{
		_ptr = sp._ptr;
		_pCount = sp._pCount;
		(*_pCount)++;
	}

	SharedPtr<T, D>operator=(SharedPtr<T, D> sp)
	{
		swap(_ptr, sp._ptr);
		swap(_pCount, sp._pCount);
		return *this;
	}
	~SharedPtr()
	{
		_Realse();
	}
public:
	void _Realse()
	{
		if (--(*_pCount) == 0)
		{
			_del(_ptr);
			delete _pCount;
		}
	}
	
public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pCount;
	D _del;
};

template<typename T>
struct DeafaultDel
{
	void operator()(T* ptr)
	{
		cout << "DeafaultDel:" << ptr << endl;
	}
};

template<typename T>
struct Free
{
	void operator()(T*ptr)
	{
		cout << "Free:" << ptr << endl;
	}
};

template<typename T>
struct Fclose
{
	void operator()(T* ptr)
	{
		cout << "Fclose:" << ptr << endl;
		fclose(ptr);
	}
};
//測試代碼
void TestDelete()
{
	int *p1 = (int*)malloc(sizeof(int));
	SharedPtr<int,Free<int>> sp1(p1);
	
	FILE* p2 = fopen("test.txt", "r");
	SharedPtr<FILE, Fclose<FILE>> sp2(p2);
}

測試結(jié)果

c++智能指針的不斷演化

2)系統(tǒng)shared_ptr的解決

struct Free
{
	void operator()(void * ptr)
	{
		cout << "Free:" << ptr << endl;
		free(ptr);	
	}
};

struct Fclose
{
	void operator ()(FILE* ptr)
	{
		cout << "Fclose" << ptr << endl;
		fclose(ptr);
	}
};
//測試代碼
void test_shared_ptr_delete()
{
	int *p1 = (int*)malloc(sizeof(int));
	shared_ptr<int> sp1(p1,Free());

	FILE* p2 = fopen("test.txt", "r");
	shared_ptr<FILE> sp2(p2,Fclose());//崩潰

}

測試結(jié)果

c++智能指針的不斷演化


總結(jié):

1、如果需要使用智能指針的話,scoped_ptr完全可以勝任。在非常特殊的情況下,例如對(duì)STL容器對(duì)象,應(yīng)該只使用shared_ptr,任何情況下都不要使用auto_ptr.

2、使用時(shí)盡量局部化,因?yàn)槠涫峭ㄟ^調(diào)用其析構(gòu)函數(shù)來實(shí)現(xiàn)資源的回收。 

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

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

AI