std::auto_ptr可以不经意间转移裸指针控制权
std::auto_ptr持有裸指针的控制权,却可以随随便便看似不经意地转移给另一个auto_ptr:
#include <iostream>
#include <memory>
using namespace std;
struct S
{
int a;
void SetA(int a)
{
this->a = a;
}
~S()
{
cout << "~S: bye-bye" << endl;
}
};
void test_auto_ptr_crash()
{
auto_ptr <S> aps(new S);
auto_ptr <S> aps2(aps); //转移对裸指针的所有权
aps2->SetA(99);
aps->SetA(100); //有可能造成程序挂掉
}
023行,aps获得并负责管理一个新的裸指针(通过new S所得);
024行,看是普通的“拷贝构造”操作,实际确实在做转移操作,aps拱手让出的原是它拥有且负责管理的裸指针
025行,aps2拥有,并可以控制该裸指针了
026行,“旧人”aps还想操作裸指针,但此时它拥有的裸指针是“nullptr”。
auto_ptr作为入参,传给函数:
再看看复杂点的情况,比如将auto_ptr作为入参,传给函数:
void foo(auto_ptr<S> aps)
{
;
}
void call_foo()
{
auto_ptr <S> aps(p);
/**< 当实参传递时,需要复制,这一复制,
原auto_ptr即失去了原有的裸指针 */
foo(aps);
cout << "===============" << endl;
aps->SetA(100);//程序通常要挂掉……
}
演示auto_ptr作为入参:
foo函数的入参是一个auto_ptr,而不是“auto_ptr <S>&”或“auto_ptr<S>*”,因此当实参传递时,需要复制,这一复制,原auto_ptr即失去了原有的裸指针,那就试试将入参改成auto_ptr的引用:
入参改为auto_ptr的引用:
void foo2(auto_ptr<S> & aps)
{
;
}
这将传递,没有发生裸指针转移所有权的事,但进入函数之后,万一又有复制的需要:
void foo2(auto_ptr<S> & aps)
{
//如果有复制,还是会被转移走的。
auto_ptr<S> aps2(aps);
}
以引用方式传递auto_ptr给某个函数,调用者更加没有安全感了,因为调用处原auto_ptr到底会不会被“转移”走,调用者完全不可控。
常量引用:
如果一个函数明确不准备转移auto_ptr入参的所有权,解决方法是“常量引用”:
/**< 明确不准备转移auto_ptr入参的所有权,解决方法是“常量引用” */
void foo3(auto_ptr <S> const& aps)
{
auto_ptr <S> aps2(aps); //编译不过去
aps.release(); //也编译不过去
//delete aps.get();//代码阻止不了某些程序员的猥琐
}
foo3函数作者感到迷惑了:我只是想以“aps”为模子复制一份,为什么编译不能过呢?
auto_ptr本身也感到委屈:“我早就说过了,我的‘复制’就是转移,是你们记不住呀”
auto_ptr类模板的拷贝构造函数:
典型的拷贝构造 | auto_ptr的拷贝构造 |
class XXX { public: ... XXX(XXX const& other); ... }; | template <typename T> class auto_ptr { public: ... auto_ptr(auto_ptr& other) ... }; |
对比两边的构造函数声明,左边入参带有const修饰,右边的没有。auto_ptr没有欺骗我们,它特意声明拷贝构造时的入参不能是常量,原因就在于它复制other时,还会修改other的内容,这是合法的,因为C++没规定拷贝构造的入参一定是const。只是这违反了直觉,违反了“拷贝构造”的语义。所以C++标准委员会决定抛弃auto_ptr。
auto_ptr作为函数返回值:
auto_ptr作为函数返回值的做法,在旧代码中很常见。
std::auto_ptr <S> CreateS()
{
...
S* ps = new S;
...
return std::auto_ptr <S> (ps);
}
这个做法挺灵活的:如果调用者就不想理会什么智能指针,那完全还可以使用裸指针接盘:
//得到智能指针管理的指针,原智能指针不再占有内存
S* sp = CreateS().release();
//按照常规方式使用,包括负责释放
delete sp;
如果调用者认同智能指针有一定的方便些,那就保留auto_ptr接下:
//保留auto_ptr,以方便自动释放
std::auto_ptr <S> asp = CreateS();
如果调用了CreateS()函数却不理会其返回值:
CreateS();//不理会返回值,反正它会被自动释放……
如果代码这么写:
S* sp = CreateS().get(); //可怕
这样写,本意上应该是想达到第一种写法的效果,但后果相当可怕。为什么?
因为用get()函数得到的裸指针后,智能指针依旧管理着裸指针,如果裸指针使用delete删除后,会导致智能指针失效。