在Effective C++中看到这个问题时才发现以前写的代码完全没有注意过这个问题
“自我赋值”发生在对象被赋值给对象本身时,例如:
class Weight
{...};
...
w=w;
虽然这看上去确实非常的蠢,但是谁也不能保证这不会发生,毕竟这是合法的,但是问题出来了,我们写的显示赋值函数一般是这样的,假设我们在类中使用了动态分配。
class Bitmap{...};
class Weight
{
...
private:
Bitmap * _pb;
};
Weight & Weight::operatot=(const Weight & rhs)
{
delete _pb;
_pb=new Bitmap(rhs._pb);
return *this;
}
好的,现在面临的问题是,*this和rhs这两个指向了同一个对象,在执行delete _pb 时同时也将rhs的Bitmap销毁了。
对此提出了两种有效的解决方法
①传统做法在operator=之前做一个“证同测试(identity test)”
Weight & Weight::operatot=(const Weight & rhs)
{
if(this == &rhs)
return *this;
delete _pb;
_pb=new Bitmap(rhs._pb);
return *this;
}
这样做完全行得通,但是为一件很少发生的事情花费额外的判断操作似乎不是很划算。
②依靠精心周到的语句顺序解决
调整最初的源代码
Weight & Weight::operatot=(const Weight & rhs)
{
Bitmap *pOrig=_pb; //记住原先的_pb
_pb=new Bitmap(rhs._pb); //令_pb指向*_pb的一个副本
delete pOrig; //删除原先的_pb
return *this;
}
再调整顺序后,在重新new之后再摧毁原来的_pb,这样的操作具有很高的异常安全性。这里有一个问题,如果new失败了怎么办?
我相信所有人一定都写过这样一段代码
int *p=new int[5];
if(p==NULL)
return -1;
当我们使用malloc/calloc分配内存时,检测返回值是否为”空指针”是一个良好的习惯,可惜的是new在默认状态下,分配失败并不会返回一个空指针,而是抛出(throw)一个异常!!
对此正确的操作有如下两个方法:
①捕获异常
try
{
_pb=new Bitmap(rhs._pb);
... //其他操作
}
catch(const bad_alloc& e )
{
return -1;
}
②标准 C++ 亦提供了一个方法来抑制 new 抛出异常,而返回空指针
int* p = new (std::nothrow) int;
// 这样如果 new 失败了,就不会抛出异常,而是返回空指针
if ( p == 0 ) // 如此这般,这个判断就有意义了
return -1;
... // 其它代码
delete p;