C++ Programmer's Cookbook

{C++ 基础} {C++ 高级} {C#界面,C++核心算法} {设计模式} {C#基础}

auto_ptr(转)

auto_ptr源码 const auto_ptr保证拥有权不能转移的实现原理?
-----------------------------------------------------------------------------------------------

在《C++标准程序库》p55,提到了auto_ptr使用了一个技巧,能够copy和复制non-const auto_ptr,但不可以copy和复制const atuo_ptr。
//auto_ptr的源码:
template
struct auto_ptr_ref
{
    _Tp1* _M_ptr;
    
      explicit
      auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
};


template
class auto_ptr
{
    private:
        _Tp* _M_ptr;
     
    public:
       typedef _Tp element_type;
     
       explicit
       auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }

       auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

       template
       auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }

       auto_ptr&
       operator=(auto_ptr& __a) throw()
       {
reset(__a.release());
return *this;
       }

       template
       auto_ptr&
       operator=(auto_ptr<_Tp1>& __a) throw()
       {
  reset(__a.release());
  return *this;
       }

       ~auto_ptr() { delete _M_ptr; }
     
       element_type&
       operator*() const throw()
       {
  _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
  return *_M_ptr;
       }
     
       element_type*
       operator->() const throw()
       {
 _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
 return _M_ptr;
       }
     
       element_type*
       get() const throw() { return _M_ptr; }
     
       element_type*
       release() throw()
       {
  element_type* __tmp = _M_ptr;
  _M_ptr = 0;
  return __tmp;
       }
     
       void
       reset(element_type* __p = 0) throw()
       {
 if (__p != _M_ptr)
 {
    delete _M_ptr;
    _M_ptr = __p;
 }
       }
     
       auto_ptr(auto_ptr_ref __ref) throw()
       : _M_ptr(__ref._M_ptr) { }
     
       auto_ptr&
       operator=(auto_ptr_ref __ref) throw()
       {
 if (__ref._M_ptr != this->get())
 {
    delete _M_ptr;
    _M_ptr = __ref._M_ptr;
 }
 return *this;
       }
     
      template
      operator auto_ptr_ref<_Tp1>() throw()
      { return auto_ptr_ref<_Tp1>(this->release()); }

      template
      operator auto_ptr<_Tp1>() throw()
      { return auto_ptr<_Tp1>(this->release()); }
};//VC7中的

---------------------------------------------------------------------------------------------------------------
// TEMPLATE CLASS auto_ptr
template
    class auto_ptr;

template
    struct auto_ptr_ref
        {    // proxy reference for auto_ptr copying
    auto_ptr_ref(auto_ptr<_Ty>& _Right)
        : _Ref(_Right)
        {    // construct from compatible auto_ptr
        }

    auto_ptr<_Ty>& _Ref;    // reference to constructor argument
    };

template
    class auto_ptr
        {    // wrap an object pointer to ensure destruction
public:
    typedef _Ty element_type;

    explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
        : _Myptr(_Ptr)
        {    // construct from object pointer
        }

    auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()
        : _Myptr(_Right.release())
        {    // construct by assuming pointer from _Right auto_ptr
        }

    auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
        : _Myptr(_Right._Ref.release())
        {    // construct by assuming pointer from _Right auto_ptr_ref
        }

    template
        operator auto_ptr<_Other>() _THROW0()
        {    // convert to compatible auto_ptr
        return (auto_ptr<_Other>(*this));
        }

    template
        operator auto_ptr_ref<_Other>() _THROW0()
        {    // convert to compatible auto_ptr_ref
        return (auto_ptr_ref<_Other>(*this));
        }

    template
        auto_ptr<_Ty>& operator=(auto_ptr<_Other>& _Right) _THROW0()
        {    // assign compatible _Right (assume pointer)
        reset(_Right.release());
        return (*this);
        }

    template
        auto_ptr(auto_ptr<_Other>& _Right) _THROW0()
        : _Myptr(_Right.release())
        {    // construct by assuming pointer from _Right
        }

    auto_ptr<_Ty>& operator=(auto_ptr<_Ty>& _Right) _THROW0()
        {    // assign compatible _Right (assume pointer)
        reset(_Right.release());
        return (*this);
        }

    auto_ptr<_Ty>& operator=(auto_ptr_ref<_Ty>& _Right) _THROW0()
        {    // assign compatible _Right._Ref (assume pointer)
        reset(_Right._Ref.release());
        return (*this);
        }

    ~auto_ptr()
        {    // destroy the object
        delete _Myptr;
        }

    _Ty& operator*() const _THROW0()
        {    // return designated value
        return (*_Myptr);
        }

    _Ty *operator->() const _THROW0()
        {    // return pointer to class object
        return (&**this);
        }

    _Ty *get() const _THROW0()
        {    // return wrapped pointer
        return (_Myptr);
        }

    _Ty *release() _THROW0()
        {    // return wrapped pointer and give up ownership
        _Ty *_Tmp = _Myptr;
        _Myptr = 0;
        return (_Tmp);
        }

    void reset(_Ty* _Ptr = 0)
        {    // destroy designated object and store new pointer
        if (_Ptr != _Myptr)
            delete _Myptr;
        _Myptr = _Ptr;
        }

private:
    _Ty *_Myptr;    // the wrapped object pointer
    };
----------------------------------------------------------------------------------------------------------
C++中的auto_ptr (修改版本) stl 文件中的 std::auto_ptr 在C++中的故事特别多, 在它的演变过程中至少出现了3个版本.
http://www.josuttis.com/libbook/auto_ptr.html 这个连接里面有它完整的故事.
VC6中STL带的auto_ptr( 带owner字段)的版本应该就是文中说的Version 2.

最新的Version里面包含了一个auto_ptr_ref, 这个是当将auto_ptr作为函数返回值和函数参数时需要引入的
一个"额外间接层".

下面是它的一些说明:

auto_ptr的拷贝构造函数和一般我们常见的不同, 它的参数rhs并不是const reference, 而是refence,

auto_ptr( /*const */ auto_ptr& rhs)
{
  ...
}

假设我们需要将一个auto_ptr作为某个函数的返回值, 例如
auto_ptr source()
{
  return auto_ptr(new int(3));
}

那么我们如何在caller中得到返回的结果呢?
理所当然的语法是:
auto_ptr p( source() );     (拷贝构造函数)
或者
auto_ptr p = source();      (拷贝构造函数)
或者
auto_ptr p = ...;           (operator=)
p = source();


但是如果没有auto_ptr_ref的存在, 上面这些行实际上应该是一个编译错误(VC6不报错), 原因是:

C++中有左值/右值之分, 函数如果返回值, 那么是r-value. 右值作为reference函数参数时, 只能是const reference.
因此source函数返回的auto_ptr作为rhs实参调用auto_ptr的拷贝构造函数时, 只能是const refernce, 但是
这个函数的签名需要rhs为reference, 因此无法编译.
举个最简单的例子:
有函数:
int foo() { return 0; }
void bar(int & i) { }
调用
int& i  = foo() ;       //错误
const int& i = foo();   //OK
bar(foo())              //错误

同理, 拷贝构造函数不过是一个特殊的"函数"而已, 我们上面的source函数返回的auto_ptr对象也只能作为一个
const auto_ptr&, 但是这个拷贝构造函数需要的参数原型是auto_ptr&, 而不是const auto_ptr& .
因此auto_ptr引入了一个'额外的间接层' auto_ptr_ref, 来完成一个从r-value到l-value之间的过渡.

基本的思路是;
提供另外一个构造函数, 接受一个以值传递的auto_ptr_ref:
auto_ptr( auto_ptr_ref ref)
{
      ....
}

然后在auto_ptr类中, 提供一个自动转型的函数
operator auto_ptr_ref ()
{
    .....
}


这样, source返回一个auto_ptr, 编译器尝试调用拷贝构造函数, 发现参数不必配(期望const), 然后发现了一个自动转型的
operator auto_ptr_ref()函数, 而后又发现通过调用该自动转型得到一个auto_ptr_ref对象后, 可以调用caller的
auto_ptr的以auto_ptr_ref为参数的非explicit的构造函数, 完成了一个auto_ptr到另外一个auto_ptr之间的复制过程.

注意一点: operator auto_ptr_ref () 不是const成员函数.
C++语法规则中对于临时变量的r-value有个诡秘的地方就是:
如果你需要将r-value保存在一个reference中, 或者作为某个refence的函数参数, 那么必须为const reference,
但是你也可以在一个完整的表达式中直接使用这个临时变量, 这种情况下该临时变量实际上并不是作为const reference对待,
因为你可以调用它的非const成员函数. 例如:

class Integer
{
public:

  void zero() { i = 0; }

private:
  int i;
};


class Integer
{
public:

  void zero() { i = 0; }

private:
  int i;
};

Integer().zero();       //创建一个Integer的临时变量, 然后调用非const成员函数.

这样, auto_ptr p( source() );

实际上发生的事情就是:
 auto_ptr p( source().operator auto_ptr_ref());

通过source()函数得到一个临时的auto_ptr对象, 然后调用其中的自动转换函数得到一个auto_ptr_ref,
即使该转型函数是非const成员函数仍然可行.

然后调用 p 对象的以auto_ptr_ref为参数的构造函数进行复制.
然后, source()函数创建的临时对象在整个表达式结束后被析构, 当然这个时候这个临时对象内部的指针已经被reset(0)了,
因为该指针的拥有权已经被p接管了. (否则会重复删除)

std::auto_ptr在很多情况下是很便利的, 例如一个函数内部需要通过new分配一个结构, 那么谁来释放是一个问题,
一种原则是谁分配, 谁释放, 但是对于这种情况显然不合适.
利用auto_ptr就简单得多了: 谁拥有谁释放, 谁都不要那么就编译器自动释放它. 例如

有个函数:
auto_ptr create_sth()
{
  auto_ptr p(new sth);
  return p;
}

调用1:
auto_ptr p = create_sth();
...
p退出作用域, 自动释放.

调用2:
create_sth();
没有人接受这个返回的对象, 那么编译器自动会调用auto_ptr的析构函数释放之.

sink也是一个作用:
例如我已经拥有一个auto_ptr对象的指针, 那么我可以定义一个sink函数, 原型如下:
void sink(auto_ptr p)
{
}

正如sink名字暗示的一样, sink函数起到一个吸收作用, 将某个外部的auto_ptr对象吸收过来,类似于宇宙中的"黑洞".
例如:
  auto_ptr p(new sth);
  sink(p);
  //这里, p指向null了, p所指的sth对象已经被sink函数"吸收"了.

当然为了防止这种情况在你不注意的情况下发生, 你可以
const auto_ptr p(new sth);
sink(p); //编译错误
auto_ptr p2 = p;        //编译错误

这样, 一个const auto_ptr的对象一旦构造完成, 永远不会失去对该对象的拥有权. "一旦拥有, 从不失去".

当然auto_ptr最重要的作用, 也是它的原始目的是为了提供异常安全.


auto_ptr
动态内存使用最多的是在C++应用程序的代码中。有过编程经验的程序员虽然都知道new操作符的使用一定要与delete匹配,在某些场合仍然可能有内存溢出。当异常被掷出时,程序的正常控制流程被改变,因此导致潜在的内存溢出。例如, 
void g() //可能掷出
  {
    if (some_condition == false)
      throw X();
  }
  void func()
  {
    string * pstr = new string;
    g(); //如果 g 掷出一个异常,内存溢出
    delete pstr; //如果 g 掷出一个异常,则此行为不能达到的代码行。
  }
  int main()
  {
    try
    {
      func();
    }
    catch(...)
    {}
  }
 
    当 g 掷出一个异常,异常处理机制展开堆栈:g()退出,同时控制被转移到 main() 的 catch(...)代码块。这时,无论怎样,func()中的delete语句都不会被执行,由此导致pstr的内存溢出。要是使用局部自动串变量,而不是使用动态分配-内存溢出就不会出现了:

string str; //局部自动对象
g(); //没有内存溢出 
    许多数据重要的结构以及应用,象链表,STL容器,串,数据库系统以及交互式应用必须使用动态内存分配,因此仍然冒着万一发生异常导致内存溢出的风险。C++标准化委员会意识到了这个漏洞并在标准库中添加了一个特殊的类模板,它就是std::auto_ptr,其目的是促使动态内存和异常之前进行平滑的交互。Auto_ptr保证当异常掷出时分配的对象(即:new操作符分配的对象)能被自动销毁,内存能被自动释放。下面我们就来讨论使用动态内存时,如何正确和有效地使用auto_ptr来避免资源溢出。这个技术适用于文件,线程,锁定以及与此类似的资源。

    Auto_ptr的定义可以在中找到。与标准库中其它的成员一样,它被声明在命名空间std::中。当你实例化auto_ptr对象时,对它进行初始化的方法是用一个指针指向动态分配的对象,下面是实例化和初始化auto_ptr对象的例子: 
include
  #include
  using namespace std;
  void func()
  {
    auto_ptr pstr (new string); /* 创建并初始化auto_ptr */
  }
 
    auto_ptr后面的尖括弧里指定auto_ptr指针的类型,在这个例子中是string。然后auto_ptr句柄的名字,在这个例子中是pstr。最后是用动态分配的对象指针初始化这个实例。注意你只能使用auto_ptr构造器的拷贝,也就是说,下面的代码是非法的: 
auto_ptr pstr  = new string; //编译出错
 
    Auto_ptr是一个模板,因此它是完全通用的。它可以指向任何类型的对象,包括基本的数据类型: 
auto_ptr pi  (new int);
 
    一旦你实例化一个auto_ptr,并用动态分配的对象地址对它进行了初始化,就可以将它当作普通的对象指针使用,例如: 
*pstr = "hello world"; //赋值
  pstr->size(); //调用成员函数
 
    之所以能这样做是因为auto_ptr重载了操作符&,*和->。不要被语法误导,记住pstr是一个对象,不是一个指针。

    auto_ptr是如何解决前面提到的内存溢出问题呢?auto_ptr的析构函数自动摧毁它绑定的动态分配对象。换句话说,当pstr的析构函数执行时,它删除构造pstr期间创建的串指针。你绝不能删除auto_ptr,因为它是一个本地对象,它的析构函数是被自动调用的。让我们看一下函数func()的修订版本,这次使用了auto_ptr: 
void func()
  {
    auto_ptr  pstr (new string);
    g();  //如果g()掷出异常,pstr 被自动摧毁
  }
 
    C++保证在堆栈展开过程中,自动存储类型的对象被自动摧毁。因此,如果g()掷出异常,pstr的析构函数将会在控制被转移到catch(...)块之前执行。因为pstr的析构函数删除其绑定的串指针,所以不会有内存溢出发生。这样我们在使用动态分配对象时,利用auto_ptr就实现了自动和安全的本地对象。

如何避免使用auto_ptr的缺陷

    auto_ptr并不是完美无缺的,它的确很方便,但也有缺陷,在使用时要注意避免。首先,不要将auto_ptr对象作为STL容器的元素。C++标准明确禁止这样做,否则可能会碰到不可预见的结果(在另文中讨论)。

    auto_ptr的另一个缺陷是将数组作为auto_ptr的参数: 
auto_ptr  pstr (new char[12] ); //数组;为定义
 
    记住不管什么时候使用数组的new操作时,必须要用delete[]来摧毁数组。因为auto_ptr的析构函数只对非数组类型起作用。所以数组是不能被正确摧毁的话,程序的行为是不明确的。总之,auto_ptr控制一个由new分配的单对象指针,仅此而已。 
--------------------------------------------------------------------------------------------------
Misusing auto_ptrs

auto_ptrs satisfy a certain need; namely, to avoid resource leaks when exception handling is used. Unfortunately, the exact behavior of auto_ptrs changed in the past and no other kind of smart pointers are provided in the C++ standard library, so people tend to misuse auto_ptrs. Here are some hints to help you use them correctly:

  1. auto_ptrs cannot share ownership.

    An auto_ptr must not refer to an object that is owned by another auto_ptr (or other object). Otherwise, if the first pointer deletes the object, the other pointer suddenly refers to a destroyed object, and any further read or write access may result in disaster.

  2. auto_ptrs are not provided for arrays.

    An auto_ptr is not allowed to refer to arrays. This is because an auto_ptr calls delete instead of delete [] for the object it owns. Note that there is no equivalent class in the C++ standard library that has the auto_ptr semantics for arrays. Instead, the library provides several container classes to handle collections of data (see Chapter 5).

  3. auto_ptrs are not "universal smart pointers."

    An auto_ptr is not designed to solve other problems for which smart pointers might be useful. In particular, they are not pointers for reference counting. (Pointers for reference counting ensure that an object gets deleted only if the last of several smart pointers that refer to that object gets destroyed.)

  4. auto_ptrs don't meet the requirements for container elements.

    An auto_ptr does not meet one of the most fundamental requirements for elements of standard containers. That is, after a copy or an assignment of an auto_ptr, source and sink are not equivalent. In fact, when an auto_ptr is assigned or copied, the source auto_ptr gets modified because it transfers its value rather than copying it. So you should not use an auto_ptr as an element of a standard container. Fortunately, the design of the language and library prevents this misuse from compiling in a standard-conforming environment.

posted on 2005-11-09 12:34 梦在天涯 阅读(1671) 评论(0)  编辑 收藏 引用 所属分类: CPlusPlus


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


公告

EMail:itech001#126.com

导航

统计

  • 随笔 - 461
  • 文章 - 4
  • 评论 - 746
  • 引用 - 0

常用链接

随笔分类

随笔档案

收藏夹

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

积分与排名

  • 积分 - 1796270
  • 排名 - 5

最新评论

阅读排行榜