auto_ptr类实现了较为安全的指针。例如在这种情况下:
void foo() {
int *ptr = new int (123);
do_something(); // may cause memory leak...
delete ptr;
}
如果在do_something()中抛出异常,且未被捕获,那么ptr指针无法被delete,造成内存泄漏。
利用C++提供的auto_ptr类改写之,则可以解决这个问题:
void foo() {
auto_ptr<int> ap(new int (123));
do_something(); // destructor will be called
}
那么auto_ptr是怎么实现的呢?打开memory头文件(这里以DevC++ 4.9.9.2版本中所带的g++编译器include目录下的memory头文件为例)一探究竟吧~
auto_ptr类是一个类模板,模板形参_Tp,拥有一个private的指针,即用户想要交给其管理的指针。
其余成员包括各种构造函数/复制构造函数/赋值操作符:
explicit auto_ptr(element_type* __p = 0);
auto_ptr(auto_ptr& __a); // 注意参数不是常规的const引用,稍候详谈
template<typename _Tp1>
auto_ptr(auto_ptr<_Tp1>& __a);
auto_ptr(auto_ptr_ref<element_type> __ref);
~auto_ptr();
auto_ptr& operator=(auto_ptr& __a);
template<typename _Tp1>
auto_ptr& operator=(auto_ptr<_Tp1>& __a);
auto_ptr& operator=(auto_ptr_ref<element_type> __ref);
auto_ptr对外提供的接口:
element_type& operator*() const;
element_type* operator->() const;
element_type* get() const;
element_type* release();
void reset(element_type* __p = 0);
还有两个类型转换操作符:
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>();
template<typename _Tp1>
operator auto_ptr<_Tp1>()
大部分代码都比较简单易懂,只需要注意auto_ptr类实现的赋值和复制行为与通常不同,完成对目标对象的赋值/复制操作的同时,源对象会成为未绑定的auto_ptr。
比较引人关注的是那个非const引用的复制构造函数。试想有这样的函数:
template<class T>
void foo(auto_ptr<T>);
template<class T>
auto_ptr<T> bar(void);
要实现这样的调用:
foo(auto_ptr<int>(new int (123)));
auto_ptr<int> ap = bar();
那么传给foo()的参数是一个临时对象,从bar()返回的参数也是临时对象。传参的时候会调用复制构造函数,而从bar()返回的时候会调用赋值操作符。两者情况类似,让我们看看复制构造函数中发生的事情。
由于临时对象是右值,所以复制构造函数的参数应该是const引用。但是,如果是const引用的话,就没办法修改源对象持有的指针了。对了,我们还有mutable关键字。如果给指针加上了mutable的修饰,倒是可以修改了,可问题是,如果源对象确实是一个const的auto_ptr怎么办呢?
来看看大师们的设计:将复制构造函数的参数设置为non-const引用,增加一个新的辅助类auto_ptr_ref
其定义如下:
/**
* A wrapper class to provide auto_ptr with reference semantics.
* For example, an auto_ptr can be assigned (or constructed from)
* the result of a function which returns an auto_ptr by value.
*
* All the auto_ptr_ref stuff should happen behind the scenes.
*/
template<typename _Tp1>
struct auto_ptr_ref
{
_Tp1* _M_ptr;
explicit
auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
};
这样的设计要实现的目标是,从一个右值构造出auto_ptr对象。
那么这个辅助类是怎样工作的呢?总的来说是两步转换,从auto_ptr到auto_ptr_ref,再到auto_ptr。还是以复制构造函数为例。在前面对foo()函数的调用中,临时对象是auto_ptr类类型,自然不会调用接受non-const引用参数的复制构造函数。那么唯一可能进行的只能是从auto_ptr_ref类进行构造了。现在那个神奇的转换操作符就起作用了:
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw() {
return auto_ptr_ref<_Tp1>(this->release());
}
在转换时,将原来的auto_ptr置成未绑定的对象,用它持有的指针构造了一个auto_ptr_ref对象。接着再用这个auto_ptr_ref对象构造出一个新的auto_ptr对象,把这个新对象传给foo()函数。
为了验证一下这个过程,修改一下memory头文件中的代码,在以下位置加上printf语句:
1. 上述的转换操作符
2. 用auto_ptr_ref类的构造函数
3. auto_ptr的析构函数
4. auto_ptr_ref的析构函数(自己加上一个析构函数定义)
然后运行这段程序:
#include <iostream>
#include <memory>
using namespace std;
template <class T>
void foo(auto_ptr<T> a) {
cout << *a << endl;
}
int main()
{
foo(auto_ptr<int>(new int (123)));
return 0;
}
输出如下:
convert to auto_ptr_ref construct an auto_ptr from auto_ptr_ref 123 destroy an auto_ptr destroy an auto_ptr_ref destroy an auto_ptr
析构的三个对象中,有临时对象、新构造出的auto_ptr对象和看不见的auto_ptr_ref对象。对于赋值操作符,也可以用相同的方法跟踪一下执行流程。只不过它调用的是参数为auto_ptr_ref的赋值操作符。
这样一来便解决了foo()和bar()函数的问题。主要根源还是在于auto_ptr类与众不同的复制和赋值行为。
OK~就写到这儿~累死人啊……第一篇啊……纯属菜鸟胡言,欢迎众大师拍砖,不喜者请绕道……EOF