为什么需要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
p
rivate: _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:

 

posted @ 2015-12-02 12:04  小天_y  阅读(761)  评论(0编辑  收藏  举报