Effective C++
———————————————————————
条款11:在operator=中处理"自我赋值"
有的人可能想了,有谁会写出 a = a;这种表达式这个条款是拿来充数的吧? 你还真的别这么说,这种情况还真的有情
况发生.比如有
的自我赋值你根本看不出来:比如:
a[i] = a[j]; //潜在的自我赋值,如果i = j的时候.
*px = *py; // 潜在的自我赋值,如果px和py恰巧指向同一个东西.
这些都不是明显的自我赋值,是"别名"带来的结果.一般而言当某段代码操作Points和reference而他们被用来"指向多
个相同类型的对
象",就需要考虑这些对象是否为同一个.实际上两个对象只要来自同一个继承体系,他们甚至不许声明
为相同类型就可能造成"别名"
因为一个base class的reference或者points可以指向derived class对象.
我们一般的operator=实现代码为:
widget& operator=(const widget& rhs)
{
delete pb; //假设pb为管理资源的指针
pb = new Bitma(*rhs.pb);
return *this;
}
但是如果不慎出现别名,那么你是先删除掉自己然后再自己对自己赋值? 让自己的指针指向一个被销毁的地方? 所以
我们需要加
上一层判断.
widget& operator=(const widget& rhs)
{
if (this == &rhs)
{
return *this;
}
delete pb; //假设pb为管理资源的指针
pb = new Bitma(*rhs.pb);
return *this;
}
这样做是一点问题都没有的,但是这个新版本会存在异常方面的麻烦.更明确的说,如果"new Bitma"导致异常,widget最终会持有
一个指针指向一块被删除的bitmap.这样的指针是有害的,你无法安全的删除它们,甚至无法安全的读取他们.唯一能对他们做的安
全事件是付出许多调试能力找出错误根源.
如果你为了让operator= 具有"异常安全性,所以我们只需要注意在复制pb所指东西之前别删除pb:
widget& operator=(const widget& rhs)
{
Bitma* porig = pb; //假设pb为管理资源的指针
pb = new Bitma(*rhs.pb);
delete porig;
return *this;
}
现在,如果"new Bitma"抛出异常,pb保持原状.即使没有证同测试,这段代码还是能够处理自我赋值,因为我们对原pb做了一件
复件,然后重新定义pb,最后pb定义好了之后,删除掉复件. 它或许不是处理"自我赋值"的最高效方法,但它行得通.
总结:
确保当对象自我赋值时operator有良好的行为,检查他是否存在自赋值.
确定任何函数如果操作一个或一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确.
条款12:复制对象时勿忘其每一个成分
设计良好之面向对象系统会将对象的内部封装起来,只留下两个函数负责对象拷贝,那就是copy构造函数和
copy assignment操作符
我称他们为copying函数.
如果你声明自己的copying函数,意思就是告诉编辑器你并不喜欢却省实现中的某些行为。编译器就会觉得被冒犯一
样,会用一种奇
怪的方式来回敬你,当你的代码出现一点错误的时候却不告诉你.
我就这样说吧,当你编写一个copying函数请确保两件事情:
1.复制所有的local成员变量.
2.调用所有base classes内的适当的copying函数.
其实两个copying函数往往拥有相同的实现本体,这可能会引诱你让某个函数去乔勇另一个函数以避免代码重复.这样
精益求精的态
度值得赞赏,但是令某个copying函数调用一个copying函数却无法让你达到你想要的目标.
令copy assignment操作符调用copy构造函数是不合理的,因为这就像试图构造一个已经存在的对象.这挺起来就很不
通顺.单纯的接
受这个建议:你不该令copy assignment操作符调用copy构造函数.
令copy构造函数调用copy assignment操作符同样没有意义.构造函数是用来初始化新对象,而assignent操作符知识
性于已初始化对
象身上.对应尚未构造好的对象赋值,就像在一个尚未初始化的对象身上做"只对已初始化对象才有意
义"的事情一样.同样无聊.
如果你发现你的copy构造函数和
copy assignment操作符有相近的代码,消除重复代码的做
法就是建立一个新的成员函数给两者调用
这样的函数往往是private而且常被命名为init. 你可以尝试这样.
总结:
Copying函数应该确保复制"对象内的所有成员变量"及"所有base class 成分"
不要尝试以某个copying函数实现另一个copying函数,应该将共同的机能放进第三个函数当中.