Effective C++学习笔记总链接
别名:有一个以上的方法指称某对象。
一般而言,如果某段代码操作pointer 和 reference 而它们被用来“指向多个相同类型的对象”,就需考虑这些对象是否为同一个。
实际上,两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”
原因:因为一个base class的reference或者pointer 可以指向一个derived class
class Base{
... };
class Derived: public Base{
... };
void doSomething(const Base& rb, Derived* pd); //rb和*pd有可能是同一对象
【后果】:如果没有处理自我赋值,在你尝试自行管理资源,可能会掉进“在停止使用资源之前意外释放了它”的陷阱。
看下面的代码,自我赋值并不安全
class Bitmap{
... };
class Widget
{
...
private:
Bitmap* pb; // 指针,指向一个从heap分配的对象
}
Widget& Widget::operator=(const Widget& rhs) // 不安全的自我赋值
{
delete pb; // 停止使用当前的Bitmap
pb = new Bitmap(*rhs.pb); // 使用rhs的Bitmap副本
return *this;
}
上述代码带来的问题:
operator= 函数内的*this(赋值的目的端) 和rhs有可能是同一个对象。此时delete销毁的不只是当前对象的Bitmap,也会销毁rhs的Bitmap。
在函数末尾,Widget内持有一个指针指向一个已被删除的对象。
在operator= 函数内的最前面的加一个“证同测试”,达到“自我赋值”的检验目的
代码如下
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) // 证同测试:
return *this; // 如果是自我赋值,就直接返回*this
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
但是上述代码的方法仍然存在异常方面的麻烦。
如果“new Bitmap”导致异常 (不论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap。
这样的指针非常有害,你无法安全地删除它们,甚至无法安全的读取它们。唯一能对它们做的安全事情是付出许多调试能量找出错误的起源。
只需要注意在复制pb所指东西之前别删除pb
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb; // 记住原先的pb
pb = new Bitmap(*rhs.pb); // 令pb指向*pb的一个副本
delete pOrig; // 删除原先的pb
return *this;
}
现在,如果“new Bitmap”抛出异常,pb保持原状。
即使没有证同测试,这段代码还是能够处理自我赋值,因为我们对原Bitmap做了份复制、然后指向新制造的那个复件、删除原Bitmap。或许它不是处理“自我赋值”最高效的办法,但它行得通。
如果你很关心效率,可以预先估计“自我赋值”的发生频率,根据情况将“证同测试” 放到函数起始处。
因为,证同测试,也会使代码变大一些(包括原始码和目标码)并导入一个新的控制流分支,都会降低执行速度。
替换方案,同样可行,copy and swap 技术,与异常安全性有密切相关(条款29)
class Widget
{
void swap(Widget& rhs); // 交换*this 和 rhs 的数据
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // 为rhs数据制作一份副本
swap(temp); // 将*this数据和上述副本的数据进行交换
return *this;
}
这个主题的另一个变奏曲
Widget& Widget::operator=(Widget rhs)
{
swap(rhs); // 将*this数据和上述副本的数据进行交换
return *this;
}
Scott Meyers 认为它为了伶俐巧妙牺牲了清晰性。
将“copying 动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更有效的代码。