智能指針 auto_ptr、scoped_ptr、shared_ptr、weak_ptr

  什麼是RAII?

RAII是Resource Acquisition Is Initialization的簡稱,是C++語言的一種管理資源、避免泄漏的慣用法。html

RAII又叫作資源分配即初始化,即:定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,能夠保證資源的正確初始化和釋放。ios

  爲何要使用RAII?

在計算機系統中,資源是數量有限且對系統正常運行具備必定做用的元素。好比:網絡套接字、互斥鎖、文件句柄和內存等等,它們屬於系統資源。因爲系統的資源是有限的,因此,咱們在編程使用系統資源時,都必須遵循一個步驟:
  1. 申請資源;
  2. 使用資源;
  3. 釋放資源。

第一步和第三步缺一不可,由於資源必需要申請才能使用的,使用完成之後,必需要釋放,若是不釋放的話,就會形成資源泄漏。編程

  什麼是智能指針?

 所謂智能指針就是智能/自動化的管理指針所指向的動態資源的釋放。它是一個類,有相似指針的功能。 數組

  智能指針的實現原理

  當類中有指針成員時,通常有兩種方式來管理指針成員:
  • 一是採用值型的方式管理,每一個類對象都保留一份指針指向的對象的拷貝;
  • 另外一種更優雅的方式是使用智能指針,從而實現指針指向的對象的共享。
  智能指針(smart pointer)的通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象的指針指向同一對象。每次建立類的新對象時,初始化指針就將引用計數置爲1;當對象做爲另外一對象的副本而建立時,拷貝構造函數拷貝指針並增長與之相應的引用計數;對一個對象進行賦值時,賦值操做符減小左操做數所指對象的引用計數(若是引用計數爲減至0,則刪除對象),並增長右操做數所指對象的引用計數;調用析構函數時,析構函數減小引用計數(若是引用計數減至0,則刪除基礎對象)。

  常見的智能指針

包括:std::auto_ptrboost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr安全

Boost庫的智能指針(ps:新的C++11標準中已經引入了unique_ptr/shared_ptr/weak_ptr):

 

  auto_ptr  獨佔全部權,轉移全部權 

第一種實現:最開始auto_ptr的成員變量主要有T* _ptr 和 bool _owner,主要實現原理是在構造對象時賦予其管理空間的全部權,在拷貝或賦值中轉移空間的全部權,在析構函數中當_owner爲true(擁有全部權)時來釋放全部權。網絡

template<typename T>
class AutoPtr
{
public:
	//構造函數
	explicit AutoPtr(T* ptr = NULL)
		:_ptr(ptr)
		, _owner(true)
	{}
	//拷貝構造
	AutoPtr(AutoPtr<T>& ap) //參數不能寫成const的,這裏要修改ap對象的成員
:_ptr(ap._ptr) , _owner(true) { ap._owner = false; //轉讓權限 } //賦值運算符重載 AutoPtr& operator=(AutoPtr<T>& ap) { if (this! = &ap) { delete this->_ptr; this->_ptr = ap._ptr; // 轉讓權限 this->_owner = true; ap._owner = false; } return *this; } //析構函數 ~AutoPtr() { // 只刪除擁有權限的指針 if (_owner) { this->_owner = false; delete this->_ptr; } } T& operator*() { return *(this->_ptr); } T* operator->() { return this->_ptr; } T* AutoPtr<T>::GetStr() { return (this->_ptr); } protected: T* _ptr; bool _owner; //權限擁有者 };

 出現的主要問題若是拷貝出來的對象比原來的對象先出做用域或先調用析構函數,則原來的對象的_owner雖然爲false,但卻在訪問一塊已經釋放的空間,緣由在於拷貝對象的釋放會致使原對象的_ptr指向的內容跟着被釋放!多線程

問題體現以下函數

ap1將析構的權限給予了ap2,因爲ap1._ptr和ap2._ptr指向同一份空間,ap2在出了if做用域以後自動被釋放,進而致使ap1._ptr也被釋放。測試

可是在if做用域以外,又對ap1._ptr進行訪問,致使程序崩潰,這時候 ap1._ptr已是一個野指針了,這就形成指針的懸掛的問題!ui

void Test()
{
		AutoPtr<int> ap1(new int(1));
		if (true)
		{
			AutoPtr<int> ap2(ap1);
		}

		//這裏的ap1._ptr已是一個野指針了,這就形成指針的懸掛的問題
		*(ap1.GetStr() )= 10;
}

  auto_ptr的第二種實現方法:仍是管理空間的全部權轉移,但這種實現方法中沒有_owner權限擁有者。構造和析構和上述實現方法相似,但拷貝和賦值後直接將_ptr賦爲空,禁止其再次訪問原來的內存空間,比較簡單粗暴。

template<typename T>
class AutoPtr
{
public:
	//構造函數
	explicit AutoPtr(T* ptr = NULL)  //不能寫成const T* ptr,不然出現const類型賦值給非const類型的問題
		:_ptr(ptr)
	{}
	//拷貝構造
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr=NULL;  
	}
	//賦值運算符重載
	AutoPtr& operator=(AutoPtr<T>& ap)
	{
		if (this! = &ap)
		{
			delete this->_ptr;
			this->_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}
	//析構函數
	~AutoPtr()
	{
		// 只刪除擁有權限的指針
		if (_ptr)
		{
			delete _ptr;
		}
	}

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

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

	T* GetStr()
	{
		return _ptr;
	}
protected:
	T* _ptr;
};

  這種實現方式很好的解決了舊版本野指針問題,可是因爲它實現了徹底的權限轉移,因此致使在拷貝構造和賦值以後只有一個指針可使用,而其餘指針都置爲NULL,使用很不方便,並且還很容易對NULL指針進行解引用,致使程序崩潰,其危害也是比較大的。

  scoped_ptr  獨佔全部權,防拷貝

scoped_ptr的實現原理是防止對象間的拷貝與賦值。具體實現是將拷貝構造函數和賦值運算符重載函數設置爲保護或私有,而且只聲明不實現,並將標誌設置爲保護或私有,防止他人在類外拷貝,簡單粗暴,可是也提升了代碼的安全性。  

template<typename T>
class  ScopedPtr
{
public:
	ScopedPtr(T* ptr = NULL)  
		:_ptr(ptr)
	{}

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

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

	T* GetStr()
	{
		return _ptr;
	}
	//析構函數
	~ScopedPtr()
	{
		if (_ptr!=NULL)
		{
			delete _ptr;
		}
	}
protected:
	//防拷貝
	ScopedPtr(ScopedPtr<T>& ap);
	ScopedPtr& operator=(ScopedPtr<T>& ap);

	T* _ptr;
};

 scoped_ptr的實現和auto_ptr很是相似,不一樣的是 scoped_ptr有着更嚴格的使用限制——不能拷貝,這就意味着scoped_ptr 是不能轉換其全部權的。

在通常的狀況下,若是不須要對於指針的內容進行拷貝,賦值操做,而只是爲了防止內存泄漏的發生,scoped_ptr徹底能夠勝任。

   shared_ptr   共享全部權,引用計數

 shared_ptr的實現原理是經過引用計數來實現,拷貝或賦值時將引用計數加1,析構時只有當引用計數減到0才釋放空間,不然只需將引用計數減1便可.

(ps:shared_ptr在新的C++11標準中叫作unique_ptr)

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* ptr=NULL)
		:_ptr(ptr)
		, _refCount(new long(1))
	{}

	~SharedPtr()
	{
		_Release();
	}

	SharedPtr(const SharedPtr<T>& sp)
		:_ptr(sp._ptr)
		, _refCount(sp._refCount)
	{
		++(*_refCount);
	}

	//傳統寫法
	/*SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if (this != &sp)
		{
			this->_Release();		
			_refCount = sp._refCount;
			_ptr = sp._ptr;
			++(*_refCount);
		}
		return *this;
	}*/

	//現代寫法
	SharedPtr<T>& operator=(SharedPtr<T> sp)
	{
		swap(_ptr, sp._ptr);
		swap(_refCount, sp._refCount);

		return *this;
	}

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

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

	T* GetPtr()
	{
		return _ptr;
	}

	long GetCount()
	{
		return *_refCount;
	}

protected:
	void _Release()
	{
		if (--(*_refCount) == 0)
		{
			//delete _ptr;
			_del(_ptr);
			delete _refCount;
		}
	}

protected:
	T* _ptr;
	long* _refCount;    //引用計數
};

  爲何引用計數要設置爲指針類型,而不設置成整型或靜態變量?  

  請看這篇文章:http://www.cnblogs.com/Lynn-Zhang/p/5400714.html

有時候看似完美的東西,每每都存在着隱含的缺陷,上面的實現仍舊存在問題!

問題以下:

  1. 在多線程環境下,引用計數的更新存在安全隱患
  2. 循環引用問題
  3. 定製刪除器

第一個比較容易理解,咱們能夠在改變引用計數的時候加上一把互斥鎖,防止多線程帶來的隱患。

第二個,循環引用問題。咱們先來看一段利用標準庫的shared_ptr 實現的代碼:

#include<iostream>
using  namespace std;
#include<memory>

struct Node
{
	shared_ptr<Node> _prev;
	shared_ptr<Node> _next;

	~Node()
	{
		cout << "delete :" <<this<< endl;
	}
};
void TestSharedPtr()
{
	shared_ptr<Node> cur(new(Node));
	shared_ptr<Node> next(new(Node));
}

  

cur和next出了做用域,都調用析構函數被釋放了。看似正確,可是若是咱們加兩句代碼,問題就出現了:

void TestSharedPtr()
{
	shared_ptr<Node> cur(new(Node));
	shared_ptr<Node> next(new(Node));
	cur->_next = next;    // 1
	next->_prev = cur;    // 2
}

  

咱們發現兩個節點都沒有被釋放!咱們來分析一下緣由:加了這兩句代碼後,這兩個節點的引用計數都增長了1。出了做用域進行析構時,兩個對象均不能釋放,由於prev的要釋放的前提是next釋放,而next的釋放又依賴於prev的釋放。最後就造成了循環引用,誰都釋放不了。

解決方案:

template<class T>
struct Node
{
public:
    ~Node()
    {
        cout << "delete:" << this << endl;
    }
public:
    weak_ptr<Node> _prev;
    weak_ptr<Node> _next;
};
 
void TestWeakPtr()
{
    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;
 
}

 

由於weak_ptr(弱引用智能指針)會對引用計數會作特殊處理(上述狀況不加1)。

第三個,定製刪除器。在shared_ptr中只能處理釋放new開闢的空間,而對於malloc,以及fopen打開的文件指針不能處理,因此提出了定製刪除器,方便釋放其餘類型的指針,而其實現則是經過仿函數(經過重載operator())來實現。

template <typename T, class Deleter = Del<T>>
class SharedPtr
{
public:
	SharedPtr(T* ptr);
	SharedPtr(T* ptr, Deleter del);
	SharedPtr(const SharedPtr<T, Deleter>& ap);
	~SharedPtr();
	//SharedPtr<T, Deleter>& operator=(const SharedPtr<T, Deleter>& ptr);//傳統寫法
	//現代寫法
	SharedPtr<T, Deleter>& operator=(SharedPtr<T, Deleter> ap);
	T& operator*()const;
	T* operator->()const;
	long GetCount()const;
	T* GetPtr()const;
protected:
	void _Realease();
protected:
	T* _ptr;
	long* _pCount;
	Deleter _del;
};

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::SharedPtr(T* ptr) :_ptr(ptr), _pCount(new long(1))
{}

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::SharedPtr(T* ptr, Deleter del) : _ptr(ptr), _pCount(new long(1)), _del(del)
{}

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::SharedPtr(const SharedPtr<T, Deleter>& ap) : _ptr(ap._ptr), _pCount(ap._pCount), _del(ap._del)
{
	++(*this->_pCount);
}

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::~SharedPtr()
{
	this->_Realease();
}

//template <typename T, class Deleter = Del<T>>//傳統寫法
//SharedPtr<T, Deleter>& SharedPtr<T, Deleter>::operator=(SharedPtr<T, Deleter> ap)
//{
//  if (this->_ptr != ap._ptr)
//  {
//      this->_Realease();
//      this->_ptr = ap._ptr;
//      this->_pCount = ap._pCount;
//      ++(*this->_pCount);
//  }
//  return *this;
//}

template <typename T, class Deleter = Del<T>>//現代寫法
SharedPtr<T, Deleter>& SharedPtr<T, Deleter>::operator=(SharedPtr<T, Deleter> ap)
{
	swap(this->_ptr, ap._ptr);
	swap(this->_pCount, ap._pCount);
	swap(this->_del, ap._del);
	return *this;
}

template <typename T, class Deleter = Del<T>>
T& SharedPtr<T, Deleter>::operator*()const
{
	return *(this->_ptr);
}

template <typename T, class Deleter = Del<T>>
T* SharedPtr<T, Deleter>::operator->()const
{
	return this->_ptr;
}

template <typename T, class Deleter = Del<T>>
long SharedPtr<T, Deleter>::GetCount()const
{
	return *(this->_pCount);
}

template <typename T, class Deleter = Del<T>>
T* SharedPtr<T, Deleter>::GetPtr()const
{
	return this->_ptr;
}

template <typename T, class Deleter = Del<T>>
void SharedPtr<T, Deleter>::_Realease()
{
	if (--(*this->_pCount) == 0)
	{
		_del(_ptr);
		delete this->_pCount;
	}
}
template<typename T>
struct Free
{
	void operator()(T* ptr)
	{	
		cout << "Free:"<<ptr<< endl;
		free(ptr);
	}
};

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

template<typename T>
struct Del
{
	void operator()(T* ptr)
	{
		cout << "Delete:" << ptr << endl;
		delete(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);
	
	SharedPtr<int> sp3(new int(1));

}

  

 總結:

auto_ptr   管理權的轉移 ->不建議使用

scopd_ptr ( unique_ptr )  防拷貝 ->簡單粗暴

shared_ptr  引用計數 ->增減引用計數,直到對象的引用計數爲0時再釋放

scoped_array 和 shared_array 管理對象數組, 因爲scopd_ptr 和 scoped_array、shared_ptr 和 shared_array 的實現都比較相似,這裏再也不贅述。須要注意的是這兩種機制都重載了operator[] .

week_ptr 弱指針 ->輔助shared_ptr解決循環引用的問題。

 

轉載必須註明做者和出處:

ProLyn http://www.cnblogs.com/Lynn-Zhang/p/5699983.html

相關文章
相關標籤/搜索