從源碼理解智能指針(一)——auto_ptr、unique_ptr

目錄

auto_ptr

構造函數

拷貝賦值

讓auto_ptr對象具有指針的行爲

析構函數

unique_ptr

_Unique_ptr_base

remove_reference

_Get_deleter_pointer_type

_Unique_ptr_base的第三個模板參數

構造函數

無參/NULL構造

用管理對象實例構造

用管理對象實例及刪除器實例構造

用另一個unique_ptr進行構造(移動構造)

release函數

賦值重載

reset函數

資源交換swap

獲取資源指針get

其它的一些重載

析構函數

刪除器deleter

管理數組資源的unique_ptr

管理數組資源的刪除器

使用自定義刪除器

總結


       在C++中,是通過new和delete來進行動態內存管理的。使用new從堆上分配空間,使用delete來釋放空間。由於這兩個工作都不是自動進行的,因此動態內存的管理是很困難的,因爲無法確保能在正確的時間對已申請的動態內存進行釋放:有可能申請之後忘記釋放,這樣就會造成內存泄漏;也有可能提前就進行了釋放,這就會引起後續的非法訪問操作。

       智能指針,就是爲了更簡單、更安全地管理動態內存。智能指針是基於“以對象管理資源”的觀念,主要有兩個關鍵點:獲得資源後立刻放進管理對象(也就是“資源取得時機便是初始化時機”,即RAII),並且管理對象調用析構函數時確保資源被釋放。這裏所說的“管理對象”,就是智能指針。實際上,智能指針就是具有指針行爲的對象

(以下源碼均源於VS2013自帶STL)

auto_ptr

       auto_ptr雖然在C++11中已經被棄用,但是通過它來理解智能指針還是非常有幫助的,它定義在xmemory文件中。

構造函數

       auto_ptr類提供了三種構造方式:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;

	explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()   //通過指針構造
		: _Myptr(_Ptr)
		{	// construct from object pointer
		}

	auto_ptr(_Myt& _Right) _THROW0()   //拷貝構造
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right auto_ptr
		}

        template<class _Other>
		auto_ptr(auto_ptr<_Other>& _Right) _THROW0()  //不同模板類型auto_ptr對象構造
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right
		}
	......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

        三者分別如下所示:

int * x = new int(3);

std::auto_ptr<int> ptr0(x);    //第一種構造

std::auto_ptr<int> ptr1(ptr0);  //第二種構造

std::auto_ptr<const int> ptr2(ptr0);    //第三種構造

對於第一種構造,是直接通過一個指針來構造,用傳入的指針初始化成員指針_Myptr變量;

對於第二種構造,是通過另一個auto_ptr對象來構造,並用傳入的對象調用release函數的返回值來初始化_Myptr;

對於第三種構造,是通過不同模板類型的auto_ptr對象來構造,也是將傳入的對象調用release函數的返回值來初始化_Myptr。

       auto_ptr的成員函數release定義如下:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	_Ty *release() _THROW0()
		{	// return wrapped pointer and give up ownership
		_Ty *_Tmp = _Myptr;
		_Myptr = 0;
		return (_Tmp);
		}
	......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

        在release函數中,會先保存指針成員_Myptr的值到_Tmp中,然後將_Myptr置0後,返回_Tmp。回到構造函數中,當使用auto_ptr對象來構造時,傳入的對象參數的_Myptr就會被置爲0,也就是NULL,而它原來的值則會被保留到新構造的對象中。相當於換了一個指針指向原地址。這一點充分體現了auto_ptr的要求:一個物件只能有一個擁有者,嚴禁一物二主(《C++標準程序庫》)。

拷貝賦值

        auto_ptr通過重載賦值運算符來實現拷貝賦值。賦值重載定義如下:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	template<class _Other>
		_Myt& operator=(auto_ptr<_Other>& _Right) _THROW0()
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}
	_Myt& operator=(_Myt& _Right) _THROW0()
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}
        ......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

       和前面的構造類似,這裏的拷貝賦值也重載了兩種:一種是相同模板類型下auto_ptr對象的拷貝賦值,另一種則是不同模板類型下auto_ptr對象的拷貝賦值。這兩種的實現都是相同的,都會調用release函數,相當於釋放了傳入對象的擁有權,並將擁有權轉移到被賦值的左值對象上。這裏還調用了一個reset函數,該函數定義如下:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	void reset(_Ty *_Ptr = 0)
		{	// destroy designated object and store new pointer
		if (_Ptr != _Myptr)
			delete _Myptr;
		_Myptr = _Ptr;
		}
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

       reset函數的作用很簡單:如果傳入的指針與當前的_Myptr指針不同,那麼就釋放_Myptr所指向的內存,不管是否相同,最後都會將傳入的指針參數賦值給_Myptr。這就相當於讓auto_ptr的指針成員_Myptr指向傳入指針參數所指向的地方。

       也就是說,拷貝賦值的作用,就是讓當前auto_ptr指向傳入賦值的參數指向的地方,並且重置該參數指針爲NULL,相當於擁有權的轉移,也體現了“一個物件只能有一個擁有者”的設計思想。

       這裏說了一個“讓auto_ptr指向的地方”,顯然,auto_ptr只是一個類/對象,它的“指向”都是通過其成員指針變量指針_Myptr實現的,按道理來說auto_ptr不應該有“指向”的說法,但是auto_ptr又卻是被稱爲智能指針,這就是因爲作爲類/對象的auto_ptr確實能有指針一樣的行爲。

讓auto_ptr對象具有指針的行爲

       簡單來說,就是要讓auto_ptr對象用起來像指針。舉個例子,定義了一個auto_ptr對象:auto_ptr<type>p(new type());通過*p可以訪問到用來初始化的type類型的對象,並且p->xxx則可以訪問用於初始化的type類型的對象中的成員。可以看到,這兩種方式完全把p當做了一個指針來使用,並且這個指針就指向用來初始化auto_ptr的參數。

        前面說過,auto_ptr的指針行爲,都是通過指針_Myptr來實現的,因此,要想讓*p和p->有意義,只需要指針_Myptr來重載“*”和“->”即可。auto_ptr是這樣做的:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;

	......

	_Ty& operator*() const _THROW0()
		{	// return designated value
		return (*get());
		}

	_Ty *operator->() const _THROW0()
		{	// return pointer to class object
		return (get());
		}

	_Ty *get() const _THROW0()
		{	// return wrapped pointer
		return (_Myptr);
		}

	......

private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

        先來看這裏有一個get函數,調用該函數得到_Myptr的值。重載“*”,使得“*p”返回的實際上是*_Myptr,而“p->”返回的則是_Myptr本身。舉個例子,如果auto_ptr的模板類型爲class A,那麼_Myptr就是指向一個A object的指針,那麼*_Myptr就是這個object,因此就會有以下結果:

      由圖可知,重載“*”和“->”,就可以通過auto_ptr對象去訪問它所指向的對象,當然,如果auto_ptr本身模板類型就是int、double這樣的,那麼(*p)就是對應的int型變量的值,而p->理應是int型變量的地址,但是由於“->”很特殊,因此光是一個“p->”並不能通過編譯,需要“p.operator->()”纔是int型變量的地址。

       這樣,就使得auto_ptr的行爲更像指針。

析構函數

       智能指針作爲“用對象管理資源”的工具,需要實現兩個關鍵:在資源獲取時就是初始化,這一點在構造函數中得以體現;第二點則是在析構函數中釋放資源。下面就來看看auto_ptr的析構函數:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	~auto_ptr() _NOEXCEPT
		{	// destroy the object
		delete _Myptr;
		}
	......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

       在析構函數中,只做了一件事:釋放_Myptr指向的地址。這樣,就保證了資源在auto_ptr對象構造時獲取,在auto_ptr對象析構時釋放,實現“智能”管理資源。

       由上可知,auto_ptr是一種獨佔性的指針,但是它最大的問題在於:向用戶提供了拷貝、賦值等操作,而這些操作的最後都會把原對象持有的指針置爲NULL,卻不給用戶任何提示。這就會導致用戶不小心對auto_ptr對象進行了拷貝、賦值等操作,卻沒有任何提示,如果用戶又不小心使用了原對象,那麼就很有可能引起問題,而這種問題是很難發現的。並且從另方面來說,既然設計原則是“嚴禁一物二主”,那麼爲何還提供拷貝、賦值這樣的操作呢?這顯然也是不符合“獨佔”語義的,更符合“交換擁有權”的語義,而unique_ptr則解決了這一問題。

unique_ptr

       unique_ptr是auto_ptr從C++11開始的一種替代品,它也延續了auto_ptr“嚴禁一物二主”的原則,不過unique_ptr更符合語義的一點,是直接禁止了拷貝構造和賦值重載,如下所示:

template<class _Ty,class _Dx>	
class unique_ptr
{
        ......
    unique_ptr(const _Myt&) = delete;       //禁用拷貝構造
    _Myt& operator=(const _Myt&) = delete;  //禁用賦值重載
}

      在分析unique_ptr源碼之前,還有一些是需要知道的。

      unique_ptr與auto_ptr相比,多了一個刪除器,顧名思義,刪除器是用來釋放unique_ptr所管理的資源的,這也是爲什麼unique_ptr的模板參數實際上有兩個(如上所示的_Ty,_Dx),其中_Ty就是我們需要unique_ptr管理資源的對象類型,而_Dx則是刪除器的類型。而事實上我們平時在定義一個unique_ptr時,只需要指定一個模板參數,這是因爲unique_ptr有一個默認的刪除器類型。在文件memory中,就定義了這樣的默認參數:

template<class _Ty,
	class _Dx = default_delete<_Ty> >
	class unique_ptr;

       關於這裏默認的刪除器default_delete,可見也是一個模板類,具體的後面再說。簡而言之,需要知道,unique_ptr有兩個模板參數,_Ty是需要管理資源的對象類型,_Dx是刪除器類型,刪除器是用來釋放unique_ptr所管理的資源的。

       如std::unique_ptr<A>up;那麼_Ty就是類A類型,_Dx就是默認的刪除器類型default_delete。

_Unique_ptr_base

       unique_ptr的實現比auto_ptr的實現複雜多了。unique_ptr還有一個父類_Unique_ptr_base。這個類的作用,是用來專門管理刪除器的。_Unique_ptr_base類有兩種定義,也可以看做是模板類的重載,它們的區別在於模板參數不同:

template<class _Ty,class _Dx,bool _Empty_deleter>
class _Unique_ptr_base  {......}

template<class _Ty,class _Dx>
class _Unique_ptr_base<_Ty, _Dx, true>  : public _Dx   {......}

       第一種_Unique_ptr_base定義了三種模板參數,_Ty和_Dx的含義與前面相同,最後還有一個bool型參數,用來描述刪除器是否爲空;而在第二種_Unique_ptr_base中就只有_Ty和_Dx兩種模板參數,但是_Unique_ptr_base後面又跟着3個模板參數_Ty,_Dx和true,這種寫法的作用就是:如果_Unique_ptr_base的第三個模板參數是true,就使用第二種定義,否則使用第一種定義。至於什麼情況下true,這需要到unique_ptr定義中才知道,不過從這個變量的名稱_Empty_deleter可以猜到,true表示刪除器爲空。現在來看下兩種_Unique_ptr_base的定義:

template<class _Ty,
	class _Dx,
	bool _Empty_deleter>
	class _Unique_ptr_base  //傳入的刪除器類型非空且與默認的刪除器不同
	{	// stores pointer and deleter
public:
	typedef typename remove_reference<_Dx>::type _Dx_noref;  //將刪除器類型去除引用特性後的類型
	typedef typename _Get_deleter_pointer_type<_Ty, _Dx_noref>::type pointer;

	_Unique_ptr_base(pointer _Ptr, _Dx _Dt)   //兩個參數的構造函數
		: _Myptr(_Ptr), _Mydel(_Dt)
		{	// construct with pointer and deleter
		}

	_Unique_ptr_base(pointer _Ptr)     //只含一個參數的構造函數
		: _Myptr(_Ptr)
		{	// construct with pointer and deleter
		}

	template<class _Ptr2,
		class _Dx2>
		_Unique_ptr_base(_Ptr2 _Ptr, _Dx2 _Dt)
		: _Myptr(_Ptr), _Mydel(_Dt)   //帶兩個參數的模板構造函數
		{	// construct with compatible pointer and deleter
		}

	template<class _Ptr2>
		_Unique_ptr_base(_Ptr2 _Ptr)   //只帶一個參數的模板構造函數
		: _Myptr(_Ptr)
		{	// construct with compatible pointer and deleter
		}

	_Dx_noref& get_deleter()     //返回刪除器,const和非const型_Unique_ptr_base對象均可調用
		{	// return reference to deleter
		return (_Mydel);
		}

	const _Dx_noref& get_deleter() const   //返回刪除器,由const型_Unique_ptr_base對象調用
		{	// return const reference to deleter
		return (_Mydel);
		}

	pointer _Myptr;	// the managed pointer   //管理的指針實例
	_Dx _Mydel;		// the deleter  //刪除器實例
	};
template<class _Ty,
	class _Dx>
	class _Unique_ptr_base<_Ty, _Dx, true>  //傳入的刪除器爲空或者就是默認的刪除器
		: public _Dx
	{	// store pointer and empty deleter
public:
	typedef _Dx _Mybase;
	typedef typename remove_reference<_Dx>::type _Dx_noref;
	typedef typename _Get_deleter_pointer_type<_Ty, _Dx_noref>::type pointer;

    _Unique_ptr_base(pointer _Ptr, _Dx _Dt) _NOEXCEPT
		: _Myptr(_Ptr), _Mybase(_Dt)
		{	// construct with pointer and deleter
		}

	template<class _Ptr2,
		class _Dx2>
		_Unique_ptr_base(_Ptr2 _Ptr, _Dx2 _Dt) _NOEXCEPT
		: _Myptr(_Ptr), _Mybase(_Dt)
		{	// construct with compatible pointer and deleter
		}

	......  //其餘兩種構造函數與上面定義的完全相同

	_Dx_noref& get_deleter() _NOEXCEPT
		{	// return reference to deleter
		return (*this);
		}

	const _Dx_noref& get_deleter() const _NOEXCEPT
		{	// return const reference to deleter
		return (*this);
		}

	pointer _Myptr;	// the managed pointer
	};

 可以看到兩種定義的不同:

1.刪除器爲空時,_Unique_ptr_base只有一個_Myptr成員,刪除器不爲空時,_Unique_ptr_base除了_Myptr成員,還有一個_Mydel成員;

2.刪除器爲空時,_Unique_ptr_base還繼承了傳入的刪除器類型(刪除器爲空,還繼承刪除器,是不是很奇怪?);

3.刪除器爲空時,獲取刪除器函數get_deleter返回的是*this,而刪除器不爲空時,獲取刪除器get_deleter返回的是_Mydel成員。

       第二點是很奇葩的,爲什麼刪除器爲空了還繼承刪除器,不過結合第三點,刪除器爲空時返回的是*this,相當於返回的是對象自己,但是很明顯_Unique_ptr_base中並沒有任何刪除器,因此可以想到,這裏*this返回的,實際上是繼承的那個刪除器。那麼既然刪除器爲空,哪來的刪除器呢?這就只有一種情況:刪除器爲空時,使用的是默認的刪除器default_delete。再回到第一點,之所以刪除器爲空時,少了一個_Mydel成員,這是因爲此時使用的是默認的刪除器,而刪除器不爲空時,就需要用到一個_Mydel成員來保存一個刪除器的實例。

       另外一點可以驗證的是:當刪除器爲空時,如果構造函數需要初始化刪除器,_Unique_ptr_base是用傳入的刪除器來初始化它所繼承的_Dx類,而當刪除器不爲空,就用傳入的刪除器類型來初始化_Mydel成員。

        這裏還有兩個問題,remove_reference和_Get_deleter_pointer_type,現在就來分析一下二者:

remove_reference

       remove_reference的定義如下:

template<class _Ty>
	struct remove_reference
	{	// remove reference
	typedef _Ty type;
	};

template<class _Ty>
	struct remove_reference<_Ty&>
	{	// remove reference
	typedef _Ty type;
	};

template<class _Ty>
	struct remove_reference<_Ty&&>
	{	// remove rvalue reference
	typedef _Ty type;
	};

      正如其名,不管參數是X、X&還是X&&,最終type都會變成X,即去除傳入類型的引用特性

      _Unique_ptr_base中的使用方式爲typedef typename remove_reference<_Dx>::type _Dx_noref;因此,_Dx_noref實際上就是刪除器類型_Dx去除引用特性後的類型。(如果刪除器類型爲A&,A&&或者A,那麼_Dx_noref的類型就是A)

_Get_deleter_pointer_type

template<class _Val,class _Ty>
struct _Get_deleter_pointer_type
{ 
	template<class _Uty> 
	static auto _Fn(int) -> _Identity<typename _Uty::pointer>; 
 
	template<class _Uty> 
	static auto _Fn(_Wrap_int) -> _Identity<_Val *>; 

	typedef decltype(_Fn<_Ty>(0)) _Decltype; 
	typedef typename _Decltype::type type; 
}

struct _Wrap_int
	{	// wraps int so that int argument is favored over _Wrap_int
	_Wrap_int(int)
		{	// do nothing
		}
	};

template<class _Ty>
	struct _Identity
	{	// map _Ty to type unchanged, without operator()
	typedef _Ty type;
	};

      這裏用到了後置返回值類型,可參考後置返回值類型分析

      _Fn支持兩種類型的參數,一種是int型參數,一種是_Wrap_int型參數。注意到_Wrap_int中定義了int型的構造函數,並且構造函數並沒有聲明爲explict,因此如果int型參數的_Fn不能用時,對_Fn傳入int型實參也是可以調用_Fn(_Wrap_int)的,因爲此時可以發生int到_Wrap_int的隱式轉換

      後面的_Decltype實際上就是_Fn<_Ty>(0)的返回值類型,如果_Fn<_Ty>(0)調用的是第一個_Fn,那麼_Decltype就是_Identity<typename _Uty::pointer>,相應的type就是_Uty::pointer;如果_Fn<_Ty>(0)調用的是第二個_Fn,那麼_Decltype就是_Identity<Val *>,而 _Unique_ptr_base中對其使用方式爲typedef typename _Get_deleter_pointer_type<_Ty, _Dx_noref>::type pointer;因此,調用_Fn<_Ty>(0)兩種不同的結果會使得type爲_Dx_noref::pointer或者是_Ty *。那麼_Fn<_Ty>(0)到底會調用哪個呢?

        按理來說,0是一個int型的值,也應該調用第一種_Fn,但是這是有前提條件的:如果_Dx_noref中沒有pointer類型,那麼第一種_Fn是無法調用的,因爲返回值類型是不成立的,要讓第一種_Fn能夠調用,那麼_Dx_noref中就必須定義一個pointer類型,如:class _Dx{typedef int pointer;......}。如果沒有,那麼0就會被隱式轉換,然後通過_Wrap_int的構造函數來調用第二個_Fn,這種情況下,最後的type就是_Ty*了。

       也就是說,對於typedef typename _Get_deleter_pointer_type<_Ty, _Dx_noref>::type pointer;中的pointer類型,如果刪除器類型_Dx中定義了pointer類型,定義的pointer就是_Dx::pointer類型,否則就是_Ty *類型,也就是unique_ptr管理的對象的指針類型。舉個例子,std::unique_ptr<A>up(new A());使用的是默認的刪除器,因此pointer就是A*類型。

       

       簡而言之,在一般情況下,_Unique_ptr_base中包含了一個unique_ptr管理的對象的指針實例_Myptr以及一個刪除器類型的實例_Mydel(如果沒有定義刪除器,那麼就是繼承自默認的刪除器)。通過_Unique_ptr_base就可以獲取到這兩種實例。         

       分析完了_Unique_ptr_base,現在再來看unique_ptr。

_Unique_ptr_base的第三個模板參數

        unique_ptr的部分定義如下:

template<class _Ty,class _Dx>	// = default_delete<_Ty>
class unique_ptr
	: private _Unique_ptr_base<_Ty, _Dx,
			is_empty<_Dx>::value
				|| is_same<default_delete<_Ty>, _Dx>::value>
	//如果傳入的刪除器類型爲空(刪除器大小爲0就是空)或者和默認的刪除器類型相同,第3個參數就是true
	{	// non-copyable pointer to an object
public:
	typedef unique_ptr<_Ty, _Dx> _Myt;
	typedef _Unique_ptr_base<_Ty, _Dx,
		is_empty<_Dx>::value
			|| is_same<default_delete<_Ty>, _Dx>::value> _Mybase;
	typedef typename _Mybase::pointer pointer;
	typedef _Ty element_type;
	typedef _Dx deleter_type;
    ......
}

       通過這段定義也可以知道“何時第三個模板參數爲true”:當刪除器類型_Dx爲空類型或者刪除器類型_Dx和默認的刪除器類型完全相同時,第三個模板參數就爲true。這裏的空類型簡單來說就是內部沒有佔空間大小的成員,舉個例子,如果沒有非靜態成員、虛表指針、虛基類指針或者繼承自一個空的類,那麼就認爲這是一個空類。

構造函數

       前面說了,如果創建unique_ptr時傳入的刪除器類型中不包含pointer類型,那麼unique_ptr中的pointer類型就是其管理的對象的指針類型。unique_ptr的構造函數都是圍繞pointer來的。

無參/NULL構造

       unique_ptr提供了多種構造函數,如下所示:

unique_ptr() _NOEXCEPT     //無參構造,如std::unique_ptr<A>up;
		: _Mybase(pointer())
		{	// default construct
		static_assert(!is_pointer<_Dx>::value,
			"unique_ptr constructed with null deleter pointer");
		}

unique_ptr(nullptr_t) _NOEXCEPT    //NULL構造,如std::unique_ptr<A>up(nullptr);
		: _Mybase(pointer())
		{	// null pointer construct
		static_assert(!is_pointer<_Dx>::value,
			"unique_ptr constructed with null deleter pointer");
		}

      這兩種對應的是無參構造和NULL構造,如果沒有傳入構造參數,那麼unique_ptr就只知道它需要管理的對象類型,但是並未指定具體的管理實例。這兩種構造方式做的都是同一件事:用一個臨時構造的pointer類型實例去構造_Unique_ptr_base,從前面_Unique_ptr_base的定義也可以知道,這實際上是初始化_Unique_ptr_base的_Myptr成員爲NULL。

用管理對象實例構造

       爲了給unique_ptr指定一個具體的管理實例,就可以通過下面一種最常用的構造函數:

explicit unique_ptr(pointer _Ptr) _NOEXCEPT  //通過指針構造,如std::unique<A>up(new A());
		: _Mybase(_Ptr)
		{	// construct with pointer
		static_assert(!is_pointer<_Dx>::value,
			"unique_ptr constructed with null deleter pointer");
		}

        這種構造方式是通過指針來構造,指針類型一般就是unique_ptr所管理的對象類型的指針,同樣的,將傳入的指針用來初始化_Unique_ptr_base的_Myptr成員。這樣,unique_ptr就有了一個具體的管理實例。

用管理對象實例及刪除器實例構造

       除了用指定的對象實例來初始_Myptr成員的構造函數,unique_ptr還提供用指定的刪除器實例來初始化_Unique_ptr_base的_Mydel實例:

unique_ptr(pointer _Ptr,
		typename _If<is_reference<_Dx>::value, _Dx,
			const typename remove_reference<_Dx>::type&>::type _Dt) _NOEXCEPT
			//如果_Dx是引用,_Dt就是_Dx本身類型,否則就是_Dx的左值引用(保證形參是實參的引用)
		: _Mybase(_Ptr, _Dt)
		{	// construct with pointer and (maybe const) deleter&
		}  //如D d;  std::unique_ptr<A,D>up(new A(),d);

unique_ptr(pointer _Ptr,
		typename remove_reference<_Dx>::type&& _Dt) _NOEXCEPT  //刪除器右值構造
		: _Mybase(_Ptr, _STD move(_Dt))
		{	// construct by moving deleter
		static_assert(!is_reference<_Dx>::value,
			"unique_ptr constructed with reference to rvalue deleter");
		}//如 std::unique_ptr<A,D>up(new A(),D());

       以上兩種分別提供了以刪除器類型的左值和右值爲參數的構造方式。需要注意的是第二種:由於第二種構造方式傳入的實參必定是一個右值,因此可以直接使用std::move來把形參也以右值的形式轉發到_Unique_ptr_base的構造函數中,而無需使用std::forward(用於實參即可能是左值,也可能是右值的情況)。雖然_Unique_ptr_base中並沒有提供右值構造方式,以後也可能會擴展,這樣的做法能完美保持實參的右值型,值得學習。

用另一個unique_ptr進行構造(移動構造)

       前面已經說過,爲了避免auto_ptr的缺點,unique_ptr是不支持拷貝構造的,那麼這裏還能用另一個unique_ptr來進行構造呢?

       由於C++11中引入了移動語義,因此雖然不能使用拷貝構造,但是可以使用移動構造。試想一下,之所以拷貝構造被禁止,是因爲拷貝本身就不符合“獨佔式”的語義(拷貝相當於會存在兩份相同的),而移動構造的語義則是將資源的擁有權從A轉移到B,這種語義完全符合“獨佔式”及auto_ptr、unique_ptr的設計原則。這就好比,一個unique_ptr不再想佔有它所管理的資源,此時讓另一個unique_ptr去接管資源,轉移擁有權後也並不會違背“獨佔式”的原則,因此移動構造對於unique_ptr來說是非常適合的。

	//相同類型unique_ptr右值構造
    unique_ptr(unique_ptr&& _Right) _NOEXCEPT 
		: _Mybase(_Right.release(),
			_STD forward<_Dx>(_Right.get_deleter()))
		{	// construct by moving _Right
		}

    //不同類型的unique_ptr右值構造
	template<class _Ty2,   
		class _Dx2,
		class = typename enable_if<!is_array<_Ty2>::value  //參數unique_ptr管理的不是數組資源
			&& is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer,
				pointer>::value  //兩種unique_ptr的pointer能夠相互轉換
			&& ((is_reference<_Dx>::value && is_same<_Dx, _Dx2>::value)
				|| (!is_reference<_Dx>::value
					&& is_convertible<_Dx2, _Dx>::value)),//二者的刪除器也必須能相互轉換或者相同
			void>::type>
		unique_ptr(unique_ptr<_Ty2, _Dx2>&& _Right) _NOEXCEPT
			: _Mybase(_Right.release(),
				_STD forward<_Dx2>(_Right.get_deleter()))
		{	// construct by moving _Right
		}

    //auto_ptr右值構造
	template<class _Ty2,
		class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value
			&& is_same<_Dx, default_delete<_Ty> >::value,
			void>::type>
		unique_ptr(auto_ptr<_Ty2>&& _Right) _NOEXCEPT
			: _Mybase(_Right.release())
		{	// construct by moving _Right
		}

        這裏提供了三種移動構造,分別爲以下情況:

1.相同類型的unique_ptr右值構造;

2.不同類型的unique_ptr右值構造,但是用來構造的unique_ptr不能是管理數組資源的,並且二者所管理的資源類型和刪除器類型必須能相互轉換

3.用auto_ptr的右值來構造,前提是auto_ptr管理的資源類型必須和被構造的unqiue_ptr的管理資源類型能夠相互轉換,並且被構造的unqiue_ptr的刪除器類型必須是默認的刪除器(這是因爲auto_ptr中不存在刪除器,所以爲了安全起見只支持默認的刪除器);

class A{...};

class B:public A{...};

class D1{...};  //刪除器

class D2:public D1{...};   //刪除器

std::unique_ptr<A>p0(new A());     //指針構造

std::unique_ptr<A>p1(std::move(p0));    //用p0的右值構造p1,屬於第1種;

std::unique_ptr<B>p2(std::move(p0));    //錯誤,因爲類型A不能轉換到類型B(父類轉換成子類不安全),屬於第2種;

std::unique_ptr<A>p3(std::move(p2));    //假設p2創建成功,p2管理B類型,類型B可以安全轉換到類型A,屬於第2種;

std::unique_ptr<A>p4(std::move(std::auto_ptr<B>()));    //用管理子類類型的auto_ptr構造管理父類類型的unique_ptr,屬於第3種。

       在使用一個對象去移動構造一個unique_ptr時,就應當知道:構造後原對象就沒有資源的擁有權了,之後就不能再通過原對象來讀取/修改資源,要麼不再使用,要麼就重新賦予新的資源,這本身也是移動構造的意義。

       在這些構造函數內部,可以看到,前面兩種都是對傳入參數的刪除器進行了forward轉發,其實這裏的forward轉發只是爲了保持刪除器參數以右值形式去初始化_Unique_ptr_base,和前面類似,只是保持了實參的右值型,但是並未實現_Unique_ptr_base的右值構造。

       然後再看第一個參數,都是將傳入的參數調用release函數後再去初始化_Myptr和_Mydel,這裏的release函數理應和auto_ptr中的release定義類似:用一個新對象來獲取傳入對象的資源擁有權並返回新對象,其定義如下:

release函數

pointer release() _NOEXCEPT
		{	// yield ownership of pointer
		pointer _Ans = this->_Myptr;
		this->_Myptr = pointer();
		return (_Ans);
		}

       所謂的轉移資源,實際上就是獲取原對象的_Myptr指針,然後讓原對象的_Myptr置爲NULL(通過pointer()實現),最後用返回的新pointer來初始化新的unique_ptr的_Myptr成員。

賦值重載

       說完了構造,再來說說賦值。

       unique_ptr通過移動構造來體現了“獨佔”的原則,同樣的,賦值也應當體現出“獨佔”。unique_ptr禁用了左值的賦值重載,但是提供了右值的賦值重載,既然參數是一個右值,說明賦值之後,參數就會失去資源的擁有權了,如下所示:

template<class _Ty2,   //不同類型的unique_ptr之間的賦值
		class _Dx2>
		typename enable_if<!is_array<_Ty2>::value
			&& is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer,
				pointer>::value,
			_Myt&>::type
		operator=(unique_ptr<_Ty2, _Dx2>&& _Right) _NOEXCEPT
		{	// assign by moving _Right
		reset(_Right.release());
		this->get_deleter() = _STD forward<_Dx2>(_Right.get_deleter());
		return (*this);
		}
//相同類型的unique_ptr之間的賦值
	_Myt& operator=(_Myt&& _Right) _NOEXCEPT  
		{	// assign by moving _Right
		if (this != &_Right)
			{	// different, do the move
			reset(_Right.release());
			this->get_deleter() = _STD forward<_Dx>(_Right.get_deleter());
			}
		return (*this);
		}

        和前面移動構造的前兩種類似,也可以用不同模板類型的unique_ptr來進行賦值,不過也需要滿足二者所管理的資源類型能夠相互轉換纔行。先來看看刪除器的賦值,實際上這裏使用的是刪除器類型中的賦值重載,將傳入的參數的刪除器賦值給當前unique_ptr的刪除器,這裏使用forward函數進行轉發,是爲了以右值類型的參數去調用刪除器中的賦值重載函數(然而目前版本的默認刪除器類型中並沒有定製右值參數的賦值重載,使用的是默認生成賦值重載函數,即可接受左值也可接收右值,但默認的賦值重載函數實現的是“拷貝”而不是移動)。

       賦值的過程中還調用了一個reset函數,reset函數的參數是傳入參數調用release的返回值,因此可以猜到,reset函數的作用,應當是用release的返回值來更新當前unique_ptr對象的_Myptr成員。現在來看看reset函數:

reset函數

void reset(pointer _Ptr = pointer()) _NOEXCEPT
		{	// establish new pointer
		pointer _Old = this->_Myptr;   //保存原來的_Myptr
		this->_Myptr = _Ptr;    //將新的參數賦值給_Myptr
		if (_Old != pointer())   //如果原來的_Myptr不是null,就調用原來的刪除器的括號重載函數
			this->get_deleter()(_Old);
		}

       這裏有個需要注意的地方,reset函數不僅用傳入的新的指針更新了原來的_Myptr,如果原來的_Myptr不是null(說明unique_ptr在調用reset前就已經擁有了其他資源),還會以原來的指針爲參數,調用刪除器中的括號重載函數,這一步的作用實際上就是釋放unique_ptr原本持有的資源,簡而言之,reset函數的作用,會讓當前的unique_ptr持有傳入的新資源的同時,還會使用刪除器釋放原來持有的資源。關於這裏如何通過刪除器釋放資源的,後面將進行分析。

資源交換swap

       從前面的移動構造、右值類型的賦值重載可以看到,unique_ptr完美的體現了“獨佔”語義,這是auto_ptr完全比不上的。更爲豐富的,unique_ptr還提供了交換資源的功能:兩個相同類型的unique_ptr,可以相互交換各自佔用的資源。由swap函數實現:

void swap(_Myt& _Right) _NOEXCEPT
		{	// swap elements
		_Swap_adl(this->_Myptr, _Right._Myptr);   //互換_Myptr
		_Swap_adl(this->get_deleter(),    //互換刪除器
			_Right.get_deleter());
		}

template<class _Ty> inline
	void _Swap_adl(_Ty& _Left, _Ty& _Right)
	{	// exchange values stored at _Left and _Right, using ADL
	swap(_Left, _Right);
	}

       swap的實現很簡單,就是交換兩個unique_ptr中的_Myptr和刪除器。

獲取資源指針get

      unique_ptr可以通過get函數來獲取_Myptr指針,該函數定義如下:

pointer get() const _NOEXCEPT
		{	// return pointer to object
		return (this->_Myptr);
		}

其它的一些重載

      unique_ptr還提供了另外三種運算符重載:

typename add_reference<_Ty>::type operator*() const  //重載*運算符
		{	// return reference to object
		return (*this->_Myptr);
		}

	pointer operator->() const _NOEXCEPT    //重載->運算符
		{	// return pointer to class object
		//return this->_Myptr;
		return (_STD pointer_traits<pointer>::pointer_to(**this));
		}


	explicit operator bool() const _NOEXCEPT   //重載布爾運算符
		{	// test for non-null pointer
		return (this->_Myptr != pointer());
		}

     前兩種重載了“*”和“->”,這樣可以使得對unique_ptr對象的使用更像是指針行爲。

     第三種重載了布爾運算符,當unique_ptr對象作爲布爾判斷條件時,可以知道該對象的_Myptr成員是否爲null(即是否持有資源),舉個例子,如下所示:

std::unique_ptr<A>p0;    //無參構造,不持有資源 則 if(p0)的判斷爲false。

std::unique_ptr<A>p1(new A());     //持有資源,則if(p1)的判斷爲true。

析構函數

       前面提到了unique_ptr中除了管理資源的對象外還有一個刪除器,但是刪除器和unique_ptr管理的資源的聯繫在哪呢?刪除器的作用,是用來釋放unique_ptr所管理的資源,因此,刪除器就應當在析構函數中得到調用,unique_ptr就是這樣做的:

~unique_ptr() _NOEXCEPT
		{	// destroy the object
		if (this->_Myptr != pointer())
			this->get_deleter()(this->_Myptr);
		}

       和前面的reset函數一樣,也是調用了刪除器的括號重載函數,通過這個函數來釋放資源。

刪除器deleter

       接下來就看看unique_ptr的刪除器是如何釋放資源的。以默認的刪除器爲例,其定義如下:

template<class _Ty>
	struct default_delete
	{	// default deleter for unique_ptr
	typedef default_delete<_Ty> _Myt;

	default_delete() _NOEXCEPT    //默認構造
		{	// default construct
		}
    //拷貝構造
	template<class _Ty2,
		class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
			void>::type>
		default_delete(const default_delete<_Ty2>&) _NOEXCEPT
		{	// construct from another default_delete
		}
    //重載括號運算符
	void operator()(_Ty *_Ptr) const _NOEXCEPT
		{	// delete a pointer
		static_assert(0 < sizeof (_Ty),
			"can't delete an incomplete type");
		delete _Ptr;
		}
	};

      刪除器類型中定義的兩種構造函數都是什麼都沒做,重點關注括號運算符的重載。這實際上就是一個仿函數,當析構函數和reset函數調用this->get_deleter()(this->_Myptr);時,實際上調用的就是這裏的括號重載函數。之所以說它是一個仿函數,是因爲這裏的this->get_deleter()返回的就是一個default_delete實例,也就相當於在調用default_delete(this->_Myptr);這就使得default_delete這個類可以像函數一樣去使用。而在這個仿函數中,則是釋放了參數指針所指向的空間,完成了資源的釋放。

管理數組資源的unique_ptr

      unique_ptr還可以管理類型爲數組的資源,其定義如下:

template<class _Ty,
	class _Dx>
	class unique_ptr<_Ty[], _Dx>
		: private _Unique_ptr_base<_Ty, _Dx,
			is_empty<_Dx>::value
				|| is_same<default_delete<_Ty[]>, _Dx>::value>
	{	// non-copyable pointer to an array object
public:
	typedef unique_ptr<_Ty[], _Dx> _Myt;
	typedef _Unique_ptr_base<_Ty, _Dx,
		is_empty<_Dx>::value
			|| is_same<default_delete<_Ty[]>, _Dx>::value> _Mybase;
	typedef typename _Mybase::pointer pointer;
	typedef _Ty element_type;
	typedef _Dx deleter_type;

	using _Mybase::get_deleter;

    //無參構造,如 std::unique_ptr<B[]>p;
	unique_ptr() _NOEXCEPT
		: _Mybase(pointer())  
		{	// default construct
		static_assert(!is_pointer<_Dx>::value,
			"unique_ptr constructed with null deleter pointer");
		}
    //指針構造,如std::unique_ptr<B[]>p(new B[10]);
	explicit unique_ptr(pointer _Ptr) _NOEXCEPT
		: _Mybase(_Ptr)
		{	// construct with pointer
		static_assert(!is_pointer<_Dx>::value,
			"unique_ptr constructed with null deleter pointer");
		}
    //用指針和刪除器實例左值構造,如D d;std::unique_ptr<B[],D>p(new B[10],d);
	unique_ptr(pointer _Ptr,
		typename _If<is_reference<_Dx>::value, _Dx,
			const typename remove_reference<_Dx>::type&>::type _Dt) _NOEXCEPT
		: _Mybase(_Ptr, _Dt)
		{	// construct with pointer and (maybe const) deleter&
		}
    //用指針和刪除器實例右值構造,如D d;std::unique_ptr<B[],D>p(new B[10],D());
	unique_ptr(pointer _Ptr,
		typename remove_reference<_Dx>::type&& _Dt) _NOEXCEPT
		: _Mybase(_Ptr, _STD move(_Dt))
		{	// construct by moving deleter
		static_assert(!is_reference<_Dx>::value,
			"unique_ptr constructed with reference to rvalue deleter");
		}
    //移動構造,如std::unique_ptr<B[]>p(new B[10]);std::unique_ptr<B[]>pp(std::move(p));
	unique_ptr(unique_ptr&& _Right) _NOEXCEPT
		: _Mybase(_Right.release(),
			_STD forward<_Dx>(_Right.get_deleter()))
		{	// construct by moving _Right
		}
    //右值賦值重載,如std::unique_ptr<B[]>p(new B[10]); p=std::unique_ptr<B[]>();
	_Myt& operator=(_Myt&& _Right) _NOEXCEPT
		{	// assign by moving _Right
		if (this != &_Right)
			{	// different, do the swap
			reset(_Right.release());
			this->get_deleter() = _STD move(_Right.get_deleter());
			}
		return (*this);
		}
    //nullptr構造
	unique_ptr(nullptr_t) _NOEXCEPT
		: _Mybase(pointer())
		{	// null pointer construct
		static_assert(!is_pointer<_Dx>::value,
			"unique_ptr constructed with null deleter pointer");
		}
    //nullptr賦值重載
	_Myt& operator=(nullptr_t) _NOEXCEPT
		{	// assign a null pointer
		reset();
		return (*this);
		}

	void reset(nullptr_t) _NOEXCEPT  //內部使用的還是管理非數組下的reset
		{	// establish new null pointer
		reset();   
		}

	void swap(_Myt& _Right) _NOEXCEPT   //交換資源
		{	// swap elements
		_Swap_adl(this->_Myptr, _Right._Myptr);
		_Swap_adl(this->get_deleter(), _Right.get_deleter());
		}

	~unique_ptr() _NOEXCEPT   //析構函數調用_Delete來釋放資源
		{	// destroy the object
		_Delete();
		}
    //重載索引運算符
	typename add_reference<_Ty>::type operator[](size_t _Idx) const
		{	// return reference to object
		return (this->_Myptr[_Idx]);
		}
    //獲取資源指針
	pointer get() const _NOEXCEPT
		{	// return pointer to object
		return (this->_Myptr);
		}
    //重載布爾運算符
	explicit operator bool() const _NOEXCEPT
		{	// test for non-null pointer
		return (this->_Myptr != pointer());
		}
    //轉移佔有權
	pointer release() _NOEXCEPT
		{	// yield ownership of pointer
		pointer _Ans = this->_Myptr;
		this->_Myptr = pointer();
		return (_Ans);
		}
    //先釋放當前資源,再用參數更新資源指針
	void reset(pointer _Ptr = pointer()) _NOEXCEPT
		{	// establish new pointer
		_Delete();
		this->_Myptr = _Ptr;
		}
    //禁用其它類型的參數構造
	template<class _Ptr2>
		explicit unique_ptr(_Ptr2) = delete;
    //禁用其它類型的參數構造
	template<class _Ptr2,
		class _Dx2>
		unique_ptr(_Ptr2, _Dx2) = delete;
    //禁用拷貝構造
	unique_ptr(const _Myt&) = delete;
    //禁用賦值
	_Myt& operator=(const _Myt&) = delete;
    //禁用帶參reset
	template<class _Ptr2>
		void reset(_Ptr2) = delete;

private:
	void _Delete()   //調用刪除器仿函數來釋放資源
		{	// delete the pointer
		if (this->_Myptr != pointer())
			this->get_deleter()(this->_Myptr);
		}
	};

       可以看到,與管理非數組資源的unique_ptr相比,管理數組資源的unique_ptr大部分函數實現都差不多的。不同的有以下幾點:

1.禁止用非模板參數類型的參數來進行構造;

2.重載了下標運算符;

管理數組資源的刪除器

      除了這兩個,默認的刪除器也有變化,由於管理的數組資源,而原來的刪除器用的是delete而不是delete[],來看下數組資源對應的刪除器:

template<class _Ty>
	struct default_delete<_Ty[]>
	{	// default deleter for unique_ptr to array of unknown size
	typedef default_delete<_Ty> _Myt;

	default_delete() _NOEXCEPT
		{	// default construct
		}

	template<class _Other>
		void operator()(_Other *) const = delete;

	void operator()(_Ty *_Ptr) const _NOEXCEPT
		{	// delete a pointer
		static_assert(0 < sizeof (_Ty),
			"can't delete an incomplete type");
		delete[] _Ptr;
		}
	};

      除了把仿函數中的delete改爲delete[]之外,這裏還禁用了以其他類型作爲模板參數的仿函數,這意味着只能使用刪除器所管理的數組資源類型的參數來調用刪除器的仿函數。舉個例子,std::unique_ptr<B[]>p;那麼傳入刪除器的仿函數的類型則只能是B *類型,而不能是A *等其它類型。

使用自定義刪除器

class mydel_array
{
public:
	template <typename T>
	void operator ()(T* x){
		std::cout << "mydel_array called" << std::endl;
		delete[] x;
	}
};
class mydel
{
public:
	template <typename T>
	void operator ()(T* x){
		std::cout << "mydel called" << std::endl;
		delete x;
	}
};

class D 
{
public:
	D(){    std::cout << "  D" << std::endl;  }
	~D(){    std::cout << " ~ D" << std::endl;    }
};

int _tmain(int argc, _TCHAR* argv[])
{
	std::cout << "begin" << std::endl;
	{
		std::unique_ptr<D, mydel>p(new D);
		
		std::unique_ptr<D[], mydel_array>pa(new D[3]);
	}
	std::cout << "end" << std::endl;
}

 

總結

       auto_ptr和unique_ptr都是獨佔式指針,auto_ptr爲了實現“獨佔式”語義,拷貝構造和賦值重載會偷偷地把原對象的資源給拿走,但是不會給用戶任何提示,這是很危險的,並且拷貝和賦值本身的語義就不符合資源的獨佔原則。

       在C++11中,用unique_ptr完全取代了auto_ptr,這完全歸功於“移動語義”的出現,unique_ptr完全禁用了auto_ptr中所使用的拷貝構造和賦值重載,但是增加了用右值進行構造的移動構造方式以及用右值進行賦值的功能,移動語義完美的貼合了“獨佔”的原則,將資源的擁有權從一方轉移到另一方,但是用戶在使用時也需要爲“移動語義”而留心:移動語義表明會放棄對當前對象所持有資源的佔有權,“移動”發生之後你不應當再試圖通過當前對象去訪問、修改資源,因爲它已經不持有任何資源了,要麼就不再使用它,要麼就讓它持有新的資源

       auto_ptr直接在析構函數中進行了資源的釋放,而unique_ptr則是通過刪除器來釋放資源,並且允許用戶自定義刪除器,如果要使用自定義的刪除器,那麼就必須定義一個仿函數類,在仿函數中進行資源的釋放。

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