RAII&智能指針

RAII:資源分配及初始化。但是這個翻譯並沒有顯示出這個慣用法的真正內涵。RAII的好處在於它提供了一種資源自動管理的方式,當出現異常,回滾等現象時,RAII可以正確的釋放資源。

內存泄漏會導致:

            1.內存耗盡 2.其他程序可能用不了了 3.程序崩潰

在資源的獲取和釋放之間,我們通常會使用資源,但常常一些不可預計的異常會在資源使用過程中產生,這就使資源沒有得到正確的釋放。但是我們其實是可以捕捉異常的,但是捕捉異常會使得代碼冗餘雜亂,量大而且可讀性比較低。

比如:

void DoSomething()
{
    int *p=new int(1);
    cout<<"DoSomething()"<<endl;
}

void Test()
{
   try
   {
      DoSomething();
   }
   catch(...)
   {
      delete p;
      throw;
   }
}

這樣短短一個代碼看起來卻很雜亂,而且也不好控制。

所以我們引出了智能指針。智能指針的實現原理就是RAII。

智能指針(smart pointer)是存儲指向動態分配(堆)對象指針的類,用於生存期控制,能夠確保自動正確的銷燬動態分配的對象,防止內存泄露。它的一種通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次創建類的新對象時,初始化指針並將引用計數置爲1;當對象作爲另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數爲減至0,則刪除對象),並增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。

下面介紹幾個Boost庫中的智能指針。

  1. AutoPtr

AutoPtr的實現主要是管理權轉移。它沒有考慮引用計數,當用一個對象構造另一個對象時,會轉移這種擁有關係。

template<class T>
class AutoPtr
{
	//friend ostream& operator<< <T>(ostream& os, const AutoPtr<T>& ap);
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
	{}
	~AutoPtr()
	{
		if (_ptr!=NULL)
		{
			delete _ptr;
		}
	}
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = NULL;
	}
public:
	AutoPtr<T>& operator=(const AutoPtr<T>& ap)
	{
		if (this != ap)
		{
			delete _ptr;
			_ptr = ap._ptr;
			ap._ptr = NULL;
			return _ptr;
		}
	}

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

private:
	T* _ptr;
};

舊版的AutoPtr在它的成員函數中有一個變量owner,在構架對象的時候owner爲true。當用它去構建新的對象時,他自己的owner變爲false,新對象的owner爲true。賦值重載的時候也是,新對象的owner是true。這樣在析構的時候只要判斷owner的狀態是否爲true,當爲true時,將這塊空間delete即可。

template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
		, owner(true)
	{}
	~AutoPtr()
	{
		if (owner)
		{
			delete _ptr;
		}
	}
	AutoPtr(AutoPtr<T>& ap)
		:_ptr(ap._ptr)
		, owner(true)
	{
		ap._ptr = NULL;
		ap.owner = false;
	}
public:
	AutoPtr<T>& operator=(const AutoPtr<T>& ap)
	{
		if (this != ap)
		{
			delete _ptr;
			_ptr = ap._ptr;
			ap.owner = false;
			owner = true;
			return _ptr;
		}
	}
}

看起來舊的比新的好。AutoPtr最大的缺點就是只能有一個指針管理一塊空間。那麼爲什麼新的還取代了舊的呢?看下面

AutoPtr<int> ap1(new int(1));
 
 if (1)
 
 {
 
  AutoPtr<int> ap2(ap1);
 
 }
 
 *ap1 = 3;

這段代碼是用舊版本實現的智能指針(ap1)指向一個動態開闢的內存,然後在if條件語句中又有一個ap2指向這塊內存,我們會知道,根據舊版的智能指針的實現原理,ap1的_owner爲false,ap2的_owner爲true。那麼除了if條件語句的局部作用域,ap2就自動調用析構函數釋放內存,那麼當我們在外面*ap1=3時,訪問到的是一塊已經被釋放了的內存,那麼程序這時就會出現問題。

如果是新版的auto_ptr,它提供了一個公有成員函數GetPtr(),可以獲取指針_ptr,當發生這種情況時,它可以先判斷_ptr是否爲空,然後纔去訪問內存。舊版本這樣做是無用的,因爲ap1的_ptr並不爲空。


2.ScopePtr//守衛指針

這個類型的指針簡單來說就是簡單粗暴的防拷貝。

template<class T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr)
		:_ptr(ptr)
	{}
	~ScopedPtr()
	{
		if (_ptr != NULL)
		{
			delete _ptr;
		}
	}
protected:
	ScopedPtr(const ScopedPtr<T>& sp);
	ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
public:
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

private:
	T* _ptr;
};

將拷貝構造函數和賦值重載函數只聲明不實現,而且是Protected.就是爲了防止在類外實現的情況發生,簡單粗暴哈哈。j_0003.gif


3.SharedPtr

採用引用計數,構造一個對象時計數器爲1,用這個對象去拷貝構造另一個新的對象時,計數器增加1.去賦值給另一個對象時,計數器同樣加1.析構時計數器減1.當計數器值爲1時,便可delete這塊空間。

template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	~SharedPtr()
	{
		_Release();
	}
	SharedPtr(SharedPtr<T>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		(*_pcount)++;
	}
public:
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			_Release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
		return *this;
	}

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

		return *(_pcount);
	}
	T* GetPtr()
	{
		return _ptr;
	}

private:
	T* _ptr;
	int* _pcount;
	Del _del;

	void _Release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete(_pcount);
		}
	}
};

在這些指針中最常用到的就是SharedPtr了。但是SharedPtr也存在問題。

  1. 線程安全(這個問題我這個級別還不能很好地解決哈哈。等着我變成大神,就辦了它。。。)

  2. 循環引用

    什麼是循環引用呢??給小夥伴舉個例子來說明一下。

void test2()
{
	SharedPtr<Node> cur(new Node(1));
	SharedPtr<Node> next(new Node(2));
	cur->_next = next;
	next->_prev = cur;
}

struct Node
{
	Node(int d)
	:_data(d)
	, _next(NULL)
	, _prev(NULL)
	{}

	int _data;
	SharedPtr<Node> _next;
	SharedPtr<Node> _prev;
};

int main()
{
	test2();
	getchar();
	return 0;
}

運行這段代碼回發現並沒有輸出。沒有輸出的原因是  cur,next的引用計數都是2,當出了test2的作用域時,分別調用析構函數,二者的引用計數都減爲1.但是cur靠next->_prev指着,next靠cur->_next指着,兩個都在等對方先delete自己的引用計數才能減到0,自己才能delete,這就導致二者都不會delete了。要解決這個問題呢,我們就要引用第四種只能指針了。。那就是WeakPtr了。WeakPtr可以說就是爲了SharedPtr準備的。因爲WeakPtr的構造函數只接受SharedPtr類型的對象。

struct Node
{
	Node(int d)
	:_data(d)
	, _next(NULL)
	, _prev(NULL)
	{}

	int _data;
	WeakPtr<Node> _next;
	WeakPtr<Node> _prev;
};

WeakPtr不增加引用計數。這樣next->_prev和cur->_next兩個指針就不會增加引用計數,也就不會出現循環引用的問題了。

3.定製刪除器

當SharedPtr類型的指針遇到其他有些類型的指針時,它就不行了。。。。哦哦。。爲什麼說他不行了呢,因爲SharedPtr只能解決new 出來的東西。那麼對於文件類型的指針呢,malloc出來的東西呢。這就需要我們爲他們量身定製解決方法啦。

在這裏,又要講到另外一個知識點了。那就是仿函數。它就是能把對象當做函數調用一樣的使用。

template<class T>
struct Less
{
   bool operator()(const T& l,const T& r)
   {
      retrun l>r;
   }
}

int main()
{
   Less less;
   cout<<less(1.2)<<endl;//使用對象去調用類裏面的函數
}

接下來就是定製刪除器

template<class T ,class Del = Delete<T>>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
	{}
	SharedPtr(T* ptr,Del del)
		:_ptr(ptr)
		, _pcount(new int(1))
		, _del(del)
	{}
	~SharedPtr()
	{
		_Release();
	}
	SharedPtr(SharedPtr<T,Del>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		(*_pcount)++;
	}
public:
	SharedPtr<T, Del>& operator=(const SharedPtr<T, Del>& sp)
	{
		if (_ptr != sp._ptr)
		{
			_Release();
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
		return *this;
	}

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

		return *(_pcount);
	}
	T* GetPtr()
	{
		return _ptr;
	}

private:
	T* _ptr;
	int* _pcount;
	Del _del;

	void _Release()
	{
		if (--(*_pcount) == 0)
		{
			_del(_ptr);
			delete(_pcount);
		}
	}
};

template<class T>
struct Free                          //free
{
	void operator()(void* ptr)
	{
		cout << "free" << endl;
		free(ptr);
		ptr = NULL;
	}
};

template<class T>
struct Delete                         //delete
{
	void operator()(T* ptr)
	{
		cout << "Del" << endl;
		delete(ptr);
		ptr = NULL;
	}
};

template<class T>
struct Fclose                            //fclose
{
	void operator()(void* ptr)
	{
		cout << "Fclose" << endl;
		fclose((FILE*)ptr);
	}
};

最後我們來總結一下:

  1. AutoPtr   管理權轉移-》不要使用

  2. ScopePtr  防拷貝-》簡單粗暴

  3. SharedPtr 引用計數-》增減引用計數,最有一個對象釋放

  4. xxxArray  管理對象數組-》operator[]

  5. WearPtr   弱指針,輔助SharedPtr.解決循環引用


說了這麼多,比較囉嗦哈j_0034.gif

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