从源码理解智能指针(一)——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则是通过删除器来释放资源,并且允许用户自定义删除器,如果要使用自定义的删除器,那么就必须定义一个仿函数类,在仿函数中进行资源的释放。

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