【问题】
考虑如下代码:哪些是好的,哪些是安全的,哪些是合法的,哪些是非法的?
auto_ptr<T> source()
{
return auto_ptr<T>(new T(1));
}
void sink(auto_ptr<T> pt)
{
}
void f()
{
auto_ptr<T> a(source);
sink(source);
sink(auto_ptr<T>(new T(1)));
vector<auto_ptr<T> > v;
v.push_back(new T(3));
v.push_back(new T(4));
v.push_back(new T(1));
v.push_back(a);
v.push_back(new T(2));
sort(v.begin(),v.end());
cout << a->Value();
}
class C
{
public:
protected:
private:
auto_ptr<CImpl> pimpl_;
};
【背景】
auto_ptr的最初动机是使得下面的代码更加安全:
void f()
{
T* pt(new T);
/*更多的代码省略*/
delete pt;
}
//用auto_ptr来代替
void f()
{
auto_ptr<T> pt(new T);
/*更多的代码省略*/
//自动调用析构函数,对象能自动删除
}
源和移交
auto_ptr的拷贝行为将所有权从源移交到了目标。在拷贝后,只有目标auto_ptr“拥有”这个指针并在适当的时候删除它,而源auto_ptr仍然包容同样的对象但并不“拥有”它于是也不用删除它(否则会有二次删除问题)。你仍然可以使用这个指针同时拥有它而不拥有它的auto_ptr的对象。
例如:
void f()
{
auto_ptr<T> pt1(new T);
auto_ptr<T> pt2;
pt2 = pt1; //pt1不再有所有权了
pt1->DoSomething(); //错误,使用了空指针
}
【答案】
1.对于source()函数是一种安全的方法,分配一个对象,并返回给调用方,其方法让调用者获得一个指针的所有权,已分配对象总能安全的删除。
2.sink()函数,以值传递方式得到了一个auto_ptr,因此获得了所有权。当sink()完成以后,局部的auto_ptr对象超出作用域就会执行删除操作。因此以上编写的sink()实际上没有对参数做任何事情,因此调用sink(pt),其实是一种别致的pt.reset(0)的形式。
如下的代码合法的:
auto_ptr<T> a(source);
sink(source);
sink(auto_ptr<T>(new T(1)));
当在泛型代码使用auto_ptr(其中只要进行复制操作),并非总能意识到各个副本并不等价,此时这个问题影响非同小可:
vector<auto_ptr<T> > v;
这是不合法的,也是不安全的。问题是,auto_ptr不满足放入容器的对象类型的要求,因为auto_pt的拷贝并不相等。
继而,如下开始发生错误:
v.push_back(new T(3));
v.push_back(new T(4));
v.push_back(new T(1));
v.push_back(a); //拷贝a,意味着a不再拥有原来的指针
v.push_back(new T(2));
sort(v.begin(),v.end());
当你调用赋值元素的泛型函数,例如sort(),函数必须能够假设各个副本是等价的。例如,sort内部至少保留一个“当前”元素的拷贝,而如果你让它能工作与auto_ptr,它必须拥有一个当前auto_ptr对象的拷贝,并继续完成余下的工作,当排序完成,临时对象被销毁,问题来了:序列中至少一个auto_ptr不再拥有它包含的指针的拥有权,实际上,这个指针已经被delete了。
所以标准委员会来解决问题。标准auto_ptr经过精心设计,如果你将auto_ptr用于标准容器,就违反了标准,为了做到这一点,标准委员会使用了一个技巧:auto_ptr的拷贝构造函数和拷贝赋值函数可以接受右值对象的非常量引用为参数,标准容器insert()函数的大部分实现都接受常量引用为参数,因此就用不上auto_ptr了。
没有所有权的auto_ptr
如下代码有问题:
cout << a->Value();
a已经被拷贝,a就不会永远所有权了,并且被置为NULL。
封装指针成员
对于封装指针成员:
class C
{
public:
protected:
private:
auto_ptr<CImpl> pimpl_;
};
auto_ptr作用封装指针类型的成员变量,使得不用在做删除指针这样的清理工作。
使用一般指针成员变量而不是auto_ptr成员时,必须为这个类提供自己的析构函数、拷贝构造函数和拷贝赋值函数,因为默认版本的行为视错误的。
const auto_ptr习惯用法
现在更进一步,你将发现一个有趣的技巧。对于const auto_ptr拷贝是非法的。
const auto_ptr<T> pt1(new T);//使用const,保证永远不被复制
auto_ptr pt2(pt1); //非法
auto_ptr pt3;
pt3 = pt1; //非法
pt1.release(); //非法
pt1.reset(new T); //非法
如果你想声明一个auto_ptr拥有不被改变并且将删除它拥有的东西,这就是解决之道。