C++ 智能指针Auto_PTR 分析
C++的动态内存的分配与释放是个挺折磨人的事情,尤其异常分支复杂时(比如一堆try catch中,各catch里需要做delete 掉相关的堆上分配的内存),极有可能产生内存泄露的情况。C++中提供了智能指针作为可选的解决方案, C++标准库中自带的智能指针是auto_ptr,它在大多数场景下是满足需求的。针对auto_ptr的缺点,boost和loki两套库都扩展出一些智能指针,并且boost中有两位幸运儿入选了tr1中(std::tr1::shared_ptr,std::tr1::weak_ptr)。本文就gcc中auto_ptr的实现做些分析,以飨自己。笔记采用注释源码的方式。
/**
* 这个wrapper类提供auto_ptr以引用语义,在下面的操作中有介绍。
*/
template<typename _Tp1>
struct auto_ptr_ref {
_Tp1* _M_ptr;
explicit auto_ptr_ref(_Tp1* __p) :
_M_ptr(__p) {
}
};
/**
* auto_ptr的实现还是很简单的,使用上也简单。在创建auto_ptr对象后,
* 通常的使用也就是调用它的*和->操作符,如下面的sample片段:
* AutoPtr<Admin> ptr1(new Admin());
* cout<<ptr1->getAge()<<endl;
* cout<<”obj:”<<*ptr1<<endl;
* 可以看到,auto_ptr中的成员函数都是throw()不抛异常的。
*/
template<typename _Tp>
class auto_ptr {
private:
_Tp* _M_ptr;//
public:
/// The pointed-to type.
typedef _Tp element_type;
/**
* 构造函数,将auto_ptr绑定到指针__p。
* __p是一个指向new出来的对象的指针,默认为0(NULL)是说auto_ptr的构造函数可以不传参构造,
* 这时成员_M_ptr=0,如果接着解引用auto_ptr对象,将Segmentation fault。当然,通常应用auto_ptr的构造
* 函数会传参的。auto_ptr提供了get函数来判断_M_ptr是否为空、reset函数重置_M_ptr指针。
* 在继承情况下,_M_ptr可以是__p的基类型。
* 构造函数声明为explicit表示禁止参数的自动类型转换(因为它们总是邪恶的)。
*
*/
explicit auto_ptr(element_type* __p = 0) throw () :
_M_ptr(__p) {
}
/**
* 辅助函数
*/
element_type* release() throw () {
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
/**
* auto_ptr的复制构造函数是很邪恶的,它的逻辑是将参数__a中的指针挪给新对象的,
* 原来的__a的内置指针被置空,接下来就不能继续操作__a引用的对象,否则就掉进出错的陷阱。
* 另外,复制构造函数的参数不是个const,因为它需要修改参数内容的。
*/
auto_ptr(auto_ptr& __a) throw () :
_M_ptr(__a.release()) {
}
/**
* 成员函数模板。好吧,我承认,我对模板也是半知半解(注意,不是一知半解)。这个函数
* 用于将继承体系中的子类型上溯成基类型。举个例子:
* 假如User是基类,Admin是派生类,那么下面的操作是ok的。
* auto_ptr<Admin> ptr2(new Admin());
* auto_ptr<User> ptr3(ptr2);
* 编译器的原理类型识别和转换的大致过程是:当模板参数类型不匹配时(_Tp1转成_Tp),
* 编译首先检查是否存在合适的类型转换操作符(auto_ptr是没有的),如果没有则检查是否
* 存在合适的成员函数模板完成类型转换。在_Tp1是_Tp的派生类的情况下,这种转换就会成功。
* 这也是说,编译器检查的是模板参数类型而不是对象的实际类型,所以,下面的例子编译就会失败:
* auto_ptr<User> ptr2(new Admin());
* auto_ptr<Admin> ptr3(ptr2);
*/
template<typename _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) throw () :
_M_ptr(__a.release()) {
}
/**
* @brief 重置管理的对象指针,如果_M_ptr不为空,会delete掉。如果重置的指针就是本身
* 的_M_ptr,就是个空操作。
* @param __p 对象指针.
*/
void reset(element_type* __p = 0) throw () {
if (__p != _M_ptr) {
delete _M_ptr;
_M_ptr = __p;
}
}
/**
* 赋值操作符,和复制构造函数一样是邪恶的,赋值操作会delete掉右值管理的对象指针。
* 如果auto_ptr对象作为函数参数传递,并且是传值,那么这个调用过程会涉及到赋值操作符
* 的调用,产生并不期待的delete外部对象的结果。
*/
auto_ptr& operator=(auto_ptr& __a) throw () {
reset(__a.release());
return *this;
}
/**
* 成员函数模板赋值操作符
*/
template<typename _Tp1> auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw () {
reset(__a.release());
return *this;
}
/**
* 析构函数,操作很明显,因为是delete,所以auto_ptr是不支持数组指针的,否则会有
* 内存泄露。
*/
~auto_ptr() {
delete _M_ptr;
}
/**
* 解引用操作符
*/
element_type& operator*() const throw () {
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}
/**
* ->操作符,是auto_ptr被用得最多的调用吧。
*/
element_type* operator->() const throw () {
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}
/**
* 返回auto_ptr管理的指针,这通常用于判断指针是否为空的情况,所以,如果要判断
* auto_ptr管理的指针是否为空,不要使用if(auto_ptr_obj){}而是使用get函数(实际上,
* 因为auto_ptr并没用定义指向element_type的dumb指针的隐式类型转换操作符,所以根本
* 编译不过if(auto_ptr_obj))。
* 但是,auto_ptr并没有禁止你进一步操作你得到的指针,甚至delete它使
* auto_ptr对象内置的指针悬空。
*/
element_type* get() const throw () {
return _M_ptr;
}
/**
* 下面的三个函数连带auto_ptr_ref是auto_ptr中的神奇之笔,因为我拍了好多次脑袋才想明白
* 是怎么的应用原理。考虑两种使用情况:
* 1)void foo(auto_ptr< User> ptr);
* 2)auto_ptr< User> func_returning_auto_ptr(…..);
* auto_ptr ptr< User> = func_returning_auto_ptr(…..);
* 对于第一种情况,当调用方式是:foo(auto_ptr< User>(new User));时,因为是传值调用,
* 而实参是个临时对象,所以需要做赋值构造对象,但auto_ptr的赋值构造函数参数并不是const的
* 所以不匹配其复制构造函数。auto_ptr采用了曲线策略,编译器接着检查类型转换操作符,
* 发现operator auto_ptr_ref<_Tp1>()是匹配的,所以将临时对象转成auto_ptr_ref,再调用
* auto_ptr(auto_ptr_ref __ref)把auto_ptr_ref转成auto_ptr。
*/
auto_ptr(auto_ptr_ref<element_type> __ref) throw () :
_M_ptr(__ref._M_ptr) {
}
template<typename _Tp1> operator auto_ptr_ref<_Tp1>() throw () {
return auto_ptr_ref<_Tp1> (this->release());
}
/**
* 和auto_ptr(auto_ptr_ref __ref)相似
*/
auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw () {
if (__ref._M_ptr != this->get()) {
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return *this;
}
/**
* 怎么说这个函数呢?我还不晓得这个转换操作符在什么时候调用呢?
*/
template<typename _Tp1> operator auto_ptr<_Tp1>() throw () {
return auto_ptr<_Tp1> (this->release());
}
};
//auto_ptr是不支持void类型的模板特化。
template<> class auto_ptr<void> {
public:
typedef void element_type;
};