C++中的auto_ptr

stl <memory> 文件中的 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<int> source()
{
  return auto_ptr<int>(new int(3));
}

那么我们如何在caller中得到返回的结果呢?
理所当然的语法是:
auto_ptr<int> p( source() );     (拷贝构造函数)
或者
auto_ptr<int> p = source();      (拷贝构造函数)
或者
auto_ptr<int> 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成员函数.

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

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

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

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

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

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

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

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

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

你可能感兴趣的:(C++,c,null,reference,编译器)