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成员函数.
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<int> p( source() );
实际上发生的事情就是:
auto_ptr<int> 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<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最重要的作用, 也是它的原始目的是为了提供异常安全.