从源码理解智能指针(一)——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;  //禁用赋值重载
}
</
  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值