C++:淺析智能指針

智能指針是爲了解決C++裏防止程序員因爲忘記釋放資源而造成內存泄漏的問題

RAII

        RAII(Resource Acquisition Is Initialization)是一種利用對象生命週期來控制程序資源(如內存、文件句柄、網絡連接、互斥量等等)的簡單技術。
        在對象構造時獲取資源,接着控制對資源的訪問使之在對象的生命週期內始終保持有效,最後在對象析構的時候釋放資源。藉此, 我們實際上把管理一份資源的責任託管給了一個對象。這種做法有兩大好處:

  • 不需要顯式地釋放資源。
  • 採用這種方式,對象所需的資源在其生命期內始終保持有效。

智能指針

一、原理:

  • RAII特性
  • 重載operator*和opertaor->,具有像指針一樣的行爲

理解:利用一個行爲很像指針的對象幫你保管資源,同時當出了對象作用域時,會自動幫你釋放,智能指針不是一個真正的指針:而是一個對象,可以使用操控指針的語句來操控這個對象。並且因爲智能指針是棧空間上類的對象。所以當程序運行結束後,會自動調用其析構函數自動釋放。這樣就不會擔心在申請資源和釋放資源中程序拋異常了。

二、分類:

指針名 優點 缺點
auto_ptr 實現簡單 不安全,進行復制後會有指針懸空現象
unique_ptr 簡單粗暴且安全 無法拷貝,僅能進行所有權轉移
shared_ptr 安全,並且能拷貝 爲了解決循環引用問題,必須藉助weak_ptr實現完整功能

      注:C++庫中的智能指針都定義在#include <memory>這個頭文件中

三、分析:

  • auto_ptr

1.實現原理:將資源管理權進行轉移,即將一個指針所指向的空間交給另一個指針。

2.缺陷:管理權一直在變,但只允許有一個指針指向該資源,兩個指針就無法指向同一塊資源。這樣的話當調用拷貝構造和賦值運算符重載的時候,將原來所管理的資源轉移給當前對象後,就斷開了原ptr與其所管理資源的聯繫,導致再通過原對象訪問資源 時就會出現問題。  

   

3.代碼實現:

template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}
	~AutoPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

	AutoPtr(AutoPtr<T>& ap)  //拷貝構造
		:_ptr(ap._ptr) //將ap中資源轉移到當前對象中
	{
		ap._ptr = nullptr; //令ap與其所管理資源斷開聯繫
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap) //賦值運算符重載
	{
		if (&this != &ap) //檢測是否給自己賦值
		{
			if (_ptr)  //釋放當前對象中的資源
				delete _ptr;

			_ptr = ap._ptr;  
			ap._ptr = nullptr;   
		}
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

 

  • unique_ptr (C++11中提供)

1.實現原理:簡單粗暴的防拷貝,即不然拷貝和賦值

2.特點:讓自己的東西無法被別人獲取

3.代碼實現:

template<class T>
class UniquePtr
{
public:
	UniquePtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}
	~UniquePtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	//C++98中防拷貝的方式:聲明成私有 並且 不進行實現
	//UniquePtr(UniquePtr<T> const &);
	//UniquePtr& operator=(UniquePtr<T>const &);


	//C++11中防拷貝的方式:採用delete來禁止默認創建的函數
	UniquePtr(UniquePtr<T>const &) = delete;
	UniquePtr& operator=(UniquePtr<T>const &) = delete;
private:
	T* _ptr;
};

 

  • shared_ptr(C++11中提供)

1.實現原理:通過引用計數的方式來實現多個shared_ptr對象之間 共享資源

  1.  在shared_ptr內部,給每個資源都維護着一份計數,用來記錄該份資源被幾個對象共享。
  2.  在對象被銷燬時(也就是析構函數調用),就說明自己不使用該資源了,對象的引用計數減一。 
  3. 如果引用計數是0,就說明自己是最後一個使用該資源的對象,必須釋放該資源;
  4. 如果不是0,就說明除了自己還有其他對象在使用該份資源,不能釋放該資源,否則其他對象就成野指針了。

     注:count的類型是int*,且它是由多個對象共有的,在對count++或--時需要加上互斥鎖,這樣才能保證線程安全。

shared_ptr的線程安全問題:

  • 智能指針對象中引用計數是多個智能指針對象共享的,兩個線程中智能指針的引用計數同時++或--,這個操作不是原子的,引用計數原來是1,++了兩次,可能還是2,這樣引用計數就錯亂了。會導致資源未釋放或者程序崩潰的問題。所以在智能指針中引用計數++、--是需要加鎖的,也就是說引用計數的操作是 線程安全的。
  • 智能指針管理的對象存放在堆上,兩個線程中同時去訪問,會導致線程安全問題。

2.缺點:雙向鏈表中可能會出現循環引用問題,導致資源空間不能完全釋放掉。

   解決方案:在引用計數的場景下,把節點中的_prev和_next改成weak_ptr就可以了。

3.代碼實現:

#include <thread>
#include <mutex>
template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr = nullptr)
		:_ptr(ptr)
		,_pCount(new int(1))
		,_pMutex(new mutex)
	{
		// 如果是一個空指針對象,則引用計數給0
		if (_ptr == nullptr)
			*_pCount = 0;
	}
	SharedPtr()
	{
		Release();
	}
	SharedPtr(const SharedPtr<T>& sp)
		:_ptr(sp._ptr)
		, _pCount(sp._pCount)
		, _pMutex(sp._pMutex)
	{
		if (_ptr) //指針對象不爲空纔可進行加引用計數
			AddCount();
	}
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)   //sp1 = sp2;
	{
		if (_ptr != sp._ptr)  //if(this != &sp)
		{
			Release(); //釋放舊資源

			//共享管理新對象的資源
			_ptr = sp._ptr;
			_pCount = sp._pCount;
			_pMutex = sp._pMutex;

			//並進行加引用計數
			if (_ptr)  
				AddCount();
		}
		return *this;
	}

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

	int UseCount()
	{
		return *_pCount;
	}
	T* Get()
	{
		return _ptr;
	}
	int AddCount()  //引用計數加1
	{
		_pMutex->lock();
		++(*_pCount);
		_pMutex->unlock();
		return *_pCount;
	}
	int SubCount()   //引用計數減1
	{
		_pMutex->lock();  
		--(*_pCount);
		_pMutex->unlock();
		return *_pCount;
	}
private:
	void Release()
	{
		if (_ptr && SubCount() == 0)   //判斷引用計數減1後是否爲0,爲0則釋放資源
		{
			delete _ptr;
			delete _pCount;
		}
	}
private:
	T* _ptr;    //指向管理資源的指針
	int* _pCount; //引用計數
	mutex* _pMutex;   //互斥鎖
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章