为什么需要auto_ptr_ref
auto_ptr:唯一拥有权
智能指针(智能指针是一个对象)保证,无论在何种情形下,只要自己被摧毁,就一定连带释放其所指资源。而由于智能型指针本身就是区域变量,所以无论是正常退出,还是异常退出,只要函数退出,它就一定会被摧毁。
只有auto_ptr可以拿来当做另一个auto_ptr 的初值,普通指针是不行的(因为没有一个自动类型转换,可以将普通指针转换成智能指针——根据一般指针生成一个auto_ptr的那个构造函数,被声明为explicit。explicit的用法参见相关内容)。
std::auto_ptr<ClassA> ptr; // create an auto_ptr ptr = new ClassA; // Error ptr = std::auto_ptr<ClassA>(new ClassA); // OK, delete old object and own new
牢记:auto_ptr的语义本身就包含了拥有权,所以如果你无意转交你的拥有权,就不要在参数列中使用auto_ptr,也不要以它作为返回值。原因如下:
(1)作为参数(实参,函数调用过程中传入)的auto_ptr会将拥有权转交给参数p(形参,函数定义中,假设为p)——“值传递”,而当函数退出时,会删除参数p所拥有的对象.
(2)以pass by reference传递的话,面对一个“透过by reference 而获得auto_ptr”的函数,你根本无法预知拥有权是否被转交.
解决方法(如果非要将auto_ptr当做参数传递的话):
(1)可以运用constant reference,避免了拥有权转交给参数p的问题;
(2)将auto_ptr定义为constant,如 “const std::auto_ptr<int> p(new int);”,避免auto_ptr在函数内被无意修改以致所有权转移问题;
这一方案使得auto_ptr比显得更安全一些。事实上,C++标准程序库的所有容器都如此,如:
template <class T> void container::insert (const T& value) // constant reference { ... x = value; // ... };
总之,为了避免拥有权的意外转移,如果你的auto_ptr在整个生命周期内都不必改变其所指对象的拥有权,你可以使用const auto_ptr。
auto_ptr不满足STL容器对其元素的要求,因为在拷贝和赋值动作之后,原本的auto_ptr和新产生的auto_ptr并不相等。拷贝和赋值之后,原本的auto_ptr会交出拥有权,而不是拷贝给新的auto_ptr(原auto_ptr交出拥有权,变成null指针了)。因此请绝对不要将auto_ptr作为标准容器的元素。
例外:我们不应该以任何形式传递auto_ptr,但当面对output操作符的时候,我们可以将auto_ptr以const reference的方式传递(只作简单输出行为,不会修改)。
在auto_ptr构造函数“explicit auto_ptr::auto_ptr(T* ptr) throw() ”中:
(1)构造完成后,*this成为ptr所指对象的唯一拥有者。不允许再有其他拥有者;
(2)如果ptr本身不是null指针,那就必须是个new返回值,因为auto_ptr析构函数会对其所拥有的对象自动调用delete;
(3)不能用new[] 所生成的array作为初值。因为auto_ptr是调用delete删除对象,而不是调用delete[]。
注意:auto_ptr的copy构造函数和assignment 函数被设计为转交auto_ptr所有权。
“auto_ptr::auto_ptr(auto_ptr& ap) throw()”,
“template<class U>auto_ptr::auto_ptr(auto_ptr<U>& ap) throw()”。
针对non-const values而设计的一个copy构造函数,生成一个auto_ptr,在入口处将ap所拥有的对象的拥有权夺取过来,ap变为null指针。这个操作改变了原对象。此函数有一个重载的member template,使得ap可通过型别自动转换,构造出合适的auto_ptr。例如,根据一个“派生类的对象”,构造出一个基类对象的auto_ptr.
auto_ptr最后一个内容涉及左值/右值,引进了auto_ptr_ref类别,使我们得以拷贝和赋值non-const auto_ptrs(包括临时对象),却不能拷贝和赋值const auto_ptrs。详细解释如下:
--------------------------------------------------------------------------------------------------------
为什么需要auto_ptr_ref
auto_ptr的拥有权 :
C++常见的智能指针有std::auto_ptr、boost::shared_ptr、boost::scoped_ptr、boost::shared_array、boost::scoped_array等。auto_ptr只是其中一种而已。但是,为什么auto_ptr才有auto_ptr_ref ,而boost::shared_ptr却没有shared_ptr_ref呢?答案与auto_ptr的特性有关。auto_ptr强调对资源的拥有权 (ownership)。也就是说,auto_ptr是"它所指对象"的拥有者。而一个对象只能属于一个拥有者,严禁一物二主,否则就是重婚罪,意料外的灾难将随之而来。
为了保证auto_ptr的拥有权唯一,auto_ptr的拷贝构造函数和赋值操作符做了这样一件事情:移除另一个auto_ptr的拥有权 。为了说明拥有权的转移 ,请看下面的代码示例:
// ---------------------------------------------------------------------------- #include <iostream> #include <memory> using namespace std; int main(int argc, char **argv){ auto_ptr<int> ptr1(new int(1)); auto_ptr<int> ptr2(ptr1); //ptr1的拥有权被转移到ptr2 auto_ptr<int> ptr3(NULL); ptr3 = ptr2; //ptr2的拥有权被转移到ptr3 cout<<ptr1.get()<<endl; //结果为0 cout<<ptr2.get()<<endl; //结果为0 cout<<*ptr3<<endl; //结果为1 } // --------------------------------------------------------------------------
auto_ptr的拷贝构造函数与赋值操作符
由于需要实现拥有权的转移,auto_ptr的拷贝构造函数和赋值操作符,与一般类的做法不太相同。我们可以看看MinGW5.1.6实现的auto_ptr源代码:
// -------------------------------------------------------------------------- /** * @brief An %auto_ptr can be constructed from another %auto_ptr. * @param a Another %auto_ptr of the same type. * * This object now @e owns the object previously owned by @a a, * which has given up ownsership. */ auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {} /** * @brief %auto_ptr assignment operator. * @param a Another %auto_ptr of the same type. * * This object now @e owns the object previously owned by @a a, * which has given up ownsership. The object that this one @e * used to own and track has been deleted. */ auto_ptr& operator=(auto_ptr& __a) throw () { reset(__a.release()); return *this; }
可以看到,auto_ptr的拷贝构造函数、赋值操作符,它们的参数都是auto_ptr& (为了转交所有权,),而不是const auto_ptr & 。
一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移 。
如果auto_ptr的拷贝构造函数和赋值操作符的参数是const auto_ptr &, 那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。
右值与const & :
假设我们想写出下面的代码:
// -------------------------------------------------------------------------- #include <iostream> #include <memory> using namespace std; int main(int argc, char **argv) { auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //使用临时对象进行拷贝构造 auto_ptr<int> ptr2(NULL); ptr2 = (auto_ptr<int>(new int(2))); //使用临时对象进行赋值 }
假设没有定义auto_ptr_ref类及相关的函数,那么这段代码将不能通过编译。主要的原因是,拷贝构造函数及赋值操作符的参数:auto_ptr<int>(new int(1))和 auto_ptr<int>(new int(2)) 都是临时对象 。临时对象属于典型的右值 ,而非const &是不能指向右值的(就是说给引用赋值) (参见《More Effective C++》 ,Item 19)。auto_ptr的拷贝构造函数及赋值操作符的参数类型恰恰是auto_ptr&,明显 非const &。
左值和右值:
左值可以出现在赋值语句的左边或右边。
右值只能出现在赋值语句的右边。(一般是临时变量、函数的非引用返回值,参见C++易混淆知识点整理 第2点)
例如 x*y是个右值,编译表达式x*y=10;则出现错误.
非const引用不能绑定右值
同理,下面的两段代码,也不会通过编译:
// -------------------------------------------------------------------------- #include <iostream> #include <memory> using namespace std; auto_ptr<int> f(); int main(int argc, char **argv) { auto_ptr<int> ptr3(f()); //使用临时对象进行拷贝构造 auto_ptr<int> ptr4(NULL); ptr4 = f(); //使用临时对象进行赋值 } // -------------------------------------------------------------------------- #include <iostream> #include <memory> using namespace std; auto_ptr<int> f(){ return auto_ptr<int>(new int(3)); //这里其实也使用临时对象进行拷贝构造 }
普通类不会遇到这个问题,是因为他们的拷贝构造函数及赋值操作符(不管是用户定义还是编译器生成的版本),参数都是const &。
auto_ptr_ref之目的
传说当年C++标准委员会的好多国家,因为这个问题都想把auto_ptr从标准库中剔除。好在Bill Gibbons和Greg Colvin创造性地提出了auto_ptr_ref,解决了这一问题,世界清静了。
auto_ptr_ref之原理
很显然,下面的构造函数,是可以接收auto_ptr临时对象的。
// 传值,而不是传引用,可以接收临时对象 auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }
但另一个问题也很显然:上述构造函数不能通过编译。如果能通过编译,就会陷入循环调用(为什么?谁看了之后理解的留下评论,谢谢)。我们稍作修改:
// -------------------------------------------------------------------------- auto_ptr(auto_ptr_ref<element_type> __ref) throw() //element_type就是auto_ptr的模板参数。 : _M_ptr(__ref._M_ptr) { }
该版本的构造函数,可以接收auto_ptr_ref的临时对象。如果auto_ptr可以隐式转换到auto_ptr_ref,那么我们就能够用auto_ptr临时对象来调用该构造函数。这个隐式转换不难实现:
// 类的类型转换运算符,在auto_ptr类里面实现,目的:将auto_ptr 隐式转换为auto_ptr_ref<_Tp1>类型 -------------------------------------------------------------------------- template<typename _Tp1> operator auto_ptr_ref<_Tp1>() throw() { return auto_ptr_ref<_Tp1>(this->release()); }
附录1:SGI STL 中auto_ptr的实现:
至此,我们可以写出下面的代码,并可以通过编译:
// -------------------------------------------------------------------------- #include <iostream> #include <memory> using namespace std; int main(int argc, char **argv) { auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //由于是临时对象,所以不会调用到“auto_ptr(auto_ptr& __a)”,而是调用auto_ptr_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; }
那么,下面的代码也可以通过编译:
// -------------------------------------------------------------------------- #include <iostream> #include <memory> using namespace std; int main(int argc, char **argv) { auto_ptr<int> ptr2(NULL); ptr2 = (auto_ptr<int>(new int(2))); //调用auto_ptr_ref版本的赋值操作符 }
auto_ptr_ref之本质
本质上,auto_ptr_ref赋予了auto_ptr“引用”的语义 。
附录1:SGI STL 中auto_ptr的实现:
#ifndef __SGI_STL_MEMORY #define __SGI_STL_MEMORY #include <stl_algobase.h> #include <stl_alloc.h> #include <stl_construct.h> #include <stl_tempbuf.h> #include <stl_uninitialized.h> #include <stl_raw_storage_iter.h> __STL_BEGIN_NAMESPACE #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && \ defined(__STL_MEMBER_TEMPLATES) template<class _Tp1> struct auto_ptr_ref { // 定义auto_ptr_ref类型
_Tp1* _M_ptr; auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {} }; #endif template <class _Tp> class auto_ptr { // 定义auto_ptr
private: _Tp* _M_ptr; public: typedef _Tp element_type; // 构造函数,原始指针类型参数 explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {} // 拷贝构造函数,接收非const引用类型参数
auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {} #ifdef __STL_MEMBER_TEMPLATES // 模板 拷贝构造函数
template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW : _M_ptr(__a.release()) {} #endif /* __STL_MEMBER_TEMPLATES */ auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW { if (&__a != this) { delete _M_ptr; _M_ptr = __a.release(); } return *this; } #ifdef __STL_MEMBER_TEMPLATES template <class _Tp1> auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW { if (__a.get() != this->get()) { delete _M_ptr; _M_ptr = __a.release(); } return *this; } #endif /* __STL_MEMBER_TEMPLATES */ ~auto_ptr() __STL_NOTHROW { delete _M_ptr; } _Tp& operator*() const __STL_NOTHROW { return *_M_ptr; } _Tp* operator->() const __STL_NOTHROW { return _M_ptr; } _Tp* get() const __STL_NOTHROW { return _M_ptr; } _Tp* release() __STL_NOTHROW { _Tp* __tmp = _M_ptr; _M_ptr = 0; return __tmp; } void reset(_Tp* __p = 0) __STL_NOTHROW { if (__p != _M_ptr) { delete _M_ptr; _M_ptr = __p; } } // According to the C++ standard, these conversions are required. Most // present-day compilers, however, do not enforce that requirement---and, // in fact, most present-day compilers do not support the language // features that these conversions rely on. #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && \ defined(__STL_MEMBER_TEMPLATES) public: // 接收auto_ptr_ref<_Tp>类型参数,或者接收auto_ptr临时对象类型参数(隐式转换成auto_ptr_ref<_Tp>类型)
auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW : _M_ptr(__ref._M_ptr) {} auto_ptr& operator=(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW { if (__ref._M_ptr != this->get()) { delete _M_ptr; _M_ptr = __ref._M_ptr; } return *this; } // 定义 auto_ptr类型转换成auto_ptr_ref类型的 类型转换运算符(可以实现隐式转换,因为没有使用关键字explicit)
template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW { return auto_ptr_ref<_Tp1>(this->release()); } template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW { return auto_ptr<_Tp1>(this->release()); } #endif /* auto ptr conversions && member templates */ }; __STL_END_NAMESPACE #endif /* __SGI_STL_MEMORY */ // Local Variables: // mode:C++ // End: