您的位置:首页 > 编程语言 > C语言/C++

深入理解C++中的智能指针auto_ptr

2014-08-12 15:12 274 查看
深入理解C++中的智能指针auto_ptr

kezunhai@gmail.com

http://blog.csdn.net/kezunhai
auto_ptr是C++标准库中(<utility>)为了解决资源泄漏的问题提供的一个智能指针类模板(注意:这只是一种简单的智能指针)。auto_ptr的实现原理其实就是RAII,在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载,使用起来就像普通的指针。


std中定义的auto_ptr代码如下:

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

	explicit auto_ptr(_Ty *_Ptr = 0) _THROW0(): _Myptr(_Ptr)
	{	// construct from object pointer
	}

	auto_ptr(auto_ptr<_Ty>& _Right) _THROW0(): _Myptr(_Right.release())
	{	// construct by assuming pointer from _Right auto_ptr
	}

	auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
	{	// construct by assuming pointer from _Right auto_ptr_ref
		_Ty *_Ptr = _Right._Ref;
		_Right._Ref = 0;	// release old
		_Myptr = _Ptr;	// reset this
	}

	template<class _Other>	operator auto_ptr<_Other>() _THROW0()
	{	// convert to compatible auto_ptr
		return (auto_ptr<_Other>(*this));
	}

	template<class _Other>	operator auto_ptr_ref<_Other>() _THROW0()
	{	// convert to compatible auto_ptr_ref
		_Other *_Cvtptr = _Myptr;	// test implicit conversion
		auto_ptr_ref<_Other> _Ans(_Cvtptr);
		_Myptr = 0;	// pass ownership to auto_ptr_ref
		return (_Ans);
	}

	template<class _Other>	auto_ptr<_Ty>& operator=(auto_ptr<_Other>& _Right) _THROW0()
	{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
	}

	template<class _Other> auto_ptr(auto_ptr<_Other>& _Right) _THROW0()
		: _Myptr(_Right.release())
	{	// construct by assuming pointer from _Right
	}

	auto_ptr<_Ty>& operator=(auto_ptr<_Ty>& _Right) _THROW0()
	{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
	}

	auto_ptr<_Ty>& operator=(auto_ptr_ref<_Ty> _Right) _THROW0()
	{	// assign compatible _Right._Ref (assume pointer)
		_Ty *_Ptr = _Right._Ref;
		_Right._Ref = 0;	// release old
		reset(_Ptr);	// set new
		return (*this);
	}

	~auto_ptr()
	{	// destroy the object
		if (_Myptr != 0)
			delete _Myptr;
	}

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

#if _HAS_ITERATOR_DEBUGGING
		if (_Myptr == 0)
			_DEBUG_ERROR("auto_ptr not dereferencable");
#endif /* _HAS_ITERATOR_DEBUGGING */

		__analysis_assume(_Myptr);

		return (*get());
	}

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

#if _HAS_ITERATOR_DEBUGGING
		if (_Myptr == 0)
			_DEBUG_ERROR("auto_ptr not dereferencable");
#endif /* _HAS_ITERATOR_DEBUGGING */

		return (get());
	}

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

	_Ty *release() _THROW0()
	{	// return wrapped pointer and give up ownership
		_Ty *_Tmp = _Myptr;
		_Myptr = 0;
		return (_Tmp);
	}

	void reset(_Ty* _Ptr = 0)
	{	// destroy designated object and store new pointer
		if (_Ptr != _Myptr && _Myptr != 0)
			delete _Myptr;
		_Myptr = _Ptr;
	}

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


1、构造函数和析构函数
auto_ptr在构造时获取对某个对象的所有权(ownership),在析构时释放该对象。我们可以这样使用auto_ptr来提高代码安全性:

int*p=new int(0);
	auto_ptr<int>ap(p);
从此我们不必关心应该何时释放p,也不用担心发生异常会有内存泄漏。这里我们有几点要注意:
1)因为auto_ptr析构的时候肯定会删除他所拥有的那个对象,所以我们就要注意了,一个萝卜一个坑,两个auto_ptr不能同时拥有同一个对象。像这样:
int*p=new int(0);
	auto_ptr<int>ap1(p);
	auto_ptr<int>ap2(p);
2)考虑下面这种用法:
int*pa=new int[10];
	auto_ptr<int>ap(pa);
因为auto_ptr的析构函数中删除指针用的是delete,而不是delete [],所以我们不应该用auto_ptr来管理一个数组指针。

3) 构造函数的explicit关键词有效阻止从一个“裸”指针隐式转换成auto_ptr类型。

2、拷贝构造与赋值
与引用计数型智能指针不同的,auto_ptr要求其对“裸”指针的完全占有性。也就是说一个“裸”指针不能同时被两个以上的auto_ptr所拥有。那么,在拷贝构造或赋值操作时,我们必须作特殊的处理来保证这个特性。auto_ptr的做法是“所有权转移”,即拷贝或赋值的源对象将失去对“裸”指针的所有权,所以,与一般拷贝构造函数,赋值函数不同, auto_ptr的拷贝构造函数,赋值函数的参数为引用而不是常引用(const
reference).当然,一个auto_ptr也不能同时拥有两个以上的“裸”指针,所以,拷贝或赋值的目标对象将先释放其原来所拥有的对象。

这里的注意点是:

1) 因为一个auto_ptr被拷贝或被赋值后,其已经失去对原对象的所有权,这个时候,对这个auto_ptr的dereference操作是不安全的。如下:
int*p=new int(0);
	auto_ptr<int>ap1(p);
	auto_ptr<int>ap2=ap1; // ap1的所有权转移到ap2
	cout<<*ap1;//错误,此时ap1只剩一个null指针在手了
这种情况较为隐蔽的情形出现在将auto_ptr作为函数参数按值传递,因为在函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),这样,传入的实参auto_ptr就失去了其对原对象的所有权,而该对象会在函数退出时被局部auto_ptr删除。如下:
void func(auto_ptr<int>ap)
	{
		cout<<*ap;
	}
	auto_ptr<int>ap1(new int(0));
	func(ap1);
	cout<<*ap1;//错误,经过f(ap1)函数调用,ap1已经不再拥有任何对象了
因为这种情况太隐蔽,太容易出错了, 所以auto_ptr作为函数参数按值传递是一定要避免的。或许大家会想到用auto_ptr的指针或引用作为函数参数或许可以,但是仔细想想,我们并不知道在函数中对传入的auto_ptr做了什么,如果当中某些操作使其失去了对对象的所有权,那么这还是可能会导致致命的执行期错误。
2)我们可以看到拷贝构造函数与赋值函数都提供了一个成员模板在不覆盖“正统”版本的情况下实现auto_ptr的隐式转换。如我们有以下两个类:
class base{};
class derived:public base{};
那么下列代码就可以通过,实现从auto_ptr<derived>到auto_ptr<base>的隐式转换,因为derived*可以转换成base*类型。
auto_ptr<base>apbase=auto_ptr<derived>(new derived);
3)因为auto_ptr不具有值语义(value semantic), 所以auto_ptr不能被用在stl标准容器中。
3、 dereference操作
dereference有两个操作,一个是返回其所拥有的对象的引用,另一个则是实现了通过auto_ptr调用其所拥有的对象的成员。如:
class CAutoPtr
{
	CAutoPtr();
	void func();
}
auto_ptr<CAutoPtr>apa( new CAutoPtr);
(*apa).func();
apa->func();
这里,我们首先要确保这个智能指针确实拥有某个对象,否则,这个操作的行为即对空指针的dereference是未定义的。

4、 辅助函数

1) get用来显式的返回auto_ptr所拥有的对象指针。我们可以发现,标准库提供的auto_ptr既不提供从“裸”指针到auto_ptr的隐式转换(构造函数为explicit),也不提供从auto_ptr到“裸”指针的隐式转换,从使用上来讲可能不那么的灵活,但考虑到其所带来的安全性,还是值得的。

2) release,用来转移所有权

3)reset,用来接收所有权,如果接收所有权的auto_ptr如果已经拥有某对象,必须先释放该对象。

5、注意,不要误用auto_ptr
1)auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。
2)auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。
3)auto_ptr只是一种简单的智能指针,如有特殊需求,需要使用其他智能指针,比如share_ptr。
4)auto_ptr不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权。

更多资料参考:
1、百度百科auto_ptr
2、auto_ptr浅析
3、关于std:auto_ptr

作者:kezunhai出处:http://blog.csdn.net/kezunhai欢迎转载或分享,但请务必声明文章出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: