auto_ptr 特性和源码解析
C++ 中 auto-ptr 特性和源码解析
简介
std::auto_ptr 包含在头文件 < memory > 中,它被用来实现对动态分配对象的自动释放。如:
1 | void test(){ |
这段代码不会造成内存泄漏,因为在ap这个对象结束生命周期时,其包含的p会被自动释放。这几乎已经是auto_ptr的全部作用了,但是还有一些小秘密,下面结合它的代码看看他的实现,挖挖它的内涵。
重复释放问题
由于auto_ptr在不同实例间没有关联,因此多个实例得到同一份指针将引起重复释放问题。
1 | int*p=new int(0); |
与指针的隐式转换
auto_ptr< Type >与 Type*指针间的隐式转换是不存在的。
首先指针转到auto_ptr是通过这个接口:
1 | typedef _Tp element_type; |
注意到explicit这个关键字,有了这个关键字于是下面的代码是通不过的,因为他没有显式的调用构造函数,与explicit违背。
1 | int*p=new int(0); |
而必须通过
1 | int*p=new int(0); |
这么做的目的只可能是为了防止误用,防止在这个变量是auto_ptr还是指针上傻傻分不清。
再一个从auto_ptr< Type >转到Type*的时候使用的是get()接口,而无法隐式转过去。
指针和取值操作
下面这个例子,如果我们对ap进行(*ap)和ap->f()操作,结果与(*p)和p->f()是否一样呢?
1 | ClassABC *p=new ClassABC(); |
我们可以通过auto_ptr的源码来知道答案, 她重载了这两个操作符,使得其操作效果和直接指针的效果是一样的:
1 | operator*() const throw() |
特殊构造函数的特殊意义
我们知道拷贝构造和赋值构造函数的定义一般是:
1 | ClassABC(const ClassABC& c); |
他们的参数都是const类型的,但是auto_ptr却不是const类型的,原因是什么呢?
1 | auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { } |
涉及到一个控制权的问题,auto_ptr假设只有一个实例是持有这个指针的,否则必然导致重复释放,所以当把一个auto_ptr实例赋值给另一个auto_ptr实例时:
1 | int*p=new int(0); |
前者(ap1)的实例必须放弃这个指针(p)而把它交给后者(ap2)。而这个放弃的操作必然会修改该实例(ap1)内部数据,于是它不能是const类型,只能是非const类型的。而非const的参数类型不能接收的有,一个是const类型的,还有一个是临时变量。
1 | // 这是接收临时变量的两个例子 |
要接收const类型没办法实现。但是接收临时变量还是有办法的,看下面:
auto_ptr_ref类
于是牛人想出了一个方案来支持临时变量的赋值。Bill Gibbons和Greg Colvin创造性地提出了auto_ptr_ref类,解决了无法传递临时变量的问题。
思路是这样的,注意推导过程:
1 | 为了接收 auto_ptr<TYPE>类型的临时变量,我可以选择的形参有const auto_ptr<TYPE>&或者auto_ptr<TYPE>& 或者auto_ptr<TYPE> 3种参数类型。 |
于是我们只能新创建一个类叫做auto_ptr_ref< TYPE >。 然后给auto_ptr增加新的构造函数:
1 | auto_ptr(auto_ptr_ref<TYPE> __ref) |
然后你应该猜到了,我们再给auto_ptr增加新的类型转换函数,支持转换到auto_ptr_ref< TYPE >。
1 | template<typename _Tp1> |
好了大功告成,临时变量auto_ptr< TYPE >先转成auto_ptr_ref< TYPE >,auto_ptr_ref< TYPE >再传到构造函数中,有了这个auto_ptr_ref类,下面的代码已经可以编过了,如果刚才前面小节中,你去试了发现明明能编过我却说不行,请不要奇怪。
1 | auto_ptr<int>ap1=auto_ptr<int>(new int(0)); |
但是对于const类型的实参还是没法运作的,因为operator auto_ptr_ref< _Tp1 >()是非const的,const对象调不到这个成员函数。
不同指针实例间的赋值
假如我们有两个实例,这两个实例的对应指针是不同的,那么我们能对他们进行赋值吗?
1 | //关键代码就是对_M_ptr这个变量进行了赋值。 |
可以看出A=B这样的表达式中,只要B对应的指针类型能够赋值给A对应的指针类型,那么这个赋值就是可行的。比如父子类之间。
1 | SON *pson = NULL; |
而反过来把FATHER赋给SON就会报错了。
命运
说了这么多,好像很高级的样子,但是std::auto_ptr在最新的c++11标准草案中被std::unique_ptr取代。主要原因是auto_ptr在进行赋值时默默进行着控制权转移,而这个动作容易导致对失去控制权的实例的错误使用。一个特别的例子是把auto_ptr放到容器中时。下偏讲unique_ptr时讲。