标签:
int x, y, z; x = y = z = 15; // 赋值连锁形式赋值采用右结合律,所以上述连锁赋值被解析为:
x = (y = (z = 15));这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x.
class Widget { public: Widget& operator=(const Widget& rhs) { ... return *this; } };这个协议不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算,例如:
class Widget { public: ... Widget& operator+=(const Widget& rhs) { // 这个协议适用于+=,-=,*=等等 ... return *this; } Widget& operator=(int rhs) { // 此函数也适用,即使参数类型不符规定 ... return *this; } };这只是个协议,并无强制性.如果不遵循它,代码一样可通过编译.然而这份协议被所有内置类型和标准程序库提供的类型共同遵守,所以最好还是遵守它.
class Widget { ... }; Widget w; ... w = w; // 赋值给自己这看起来有点愚蠢,但它合法,所以不要认定客户绝不会那么做.此外赋值动作并不总是那么可被一眼辨识出来,例如:
a[i] = a[j]; // 潜在的自我赋值而过i和j具有相同的值,这便是个自我赋值.再看:
*px = *py; // 潜在的自我赋值如果px和py恰巧指向同一个东西,这也是自我赋值.这些并不明显的自我赋值,是"别名"(aliasing)带来的结果:所谓"别名"就是"有一个以上的方法指向某对象".一般而言如果某段代码操作pointers或references而它们被用来"指向多个相同类型的对象",就需要考虑这些对象是否为同一个.实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成"别名",因为一个base class 的reference或pointer可以指向一个derived class 对象:
class Base { ... }; class Derived : public Base { ... }; void doSomething(const Base& rb, Derived* pd); // rb和*pd可能是同一对象如果尝试自行管理资源,可能会掉进"在停止使用资源之前以外释放了它"的陷阱.假设建立一个 clas 来保存一个指针指向一块动态分配的位图:
class Bitmap { ... }; class Widget { ... private: Bitmap* pb; // 指针,指向一个从heap分配而得的对象 };下面是 operator= 实现代码,表面上看起来合理,但自我赋值出现时就不完全:
Widget &Widget::operator=(const Widget& rhs) { // 一份不安全的operator=实现版本 delete pb; pb = new Bitmap(&rhs.pb); return &this; }这里的自我赋值问题是,operator=函数内的*this(赋值的目的端)和rhs有可能是同一个对象.如果这样的话,delete 就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap.在函数末尾,Widget发现自己持有一个指针指向一个已被删除的对象.
Widget& Widget::operator=(const Widget& rhs) { if (this == &rhs) return *this; delete pb; pb = new Bitmap(*rhs.pb); return *this; }这样做行得通.但这个新版本仍然存在异常方面的麻烦.如果"new Bitmap"导致异常(不论是因为分配时内存不够或因为Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap.这样的指针有害,无法安全地删除它们,甚至无法安全地读取它们.
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做了一份复件,删除原bimap,然后指向新制造的那个复件.它或许不是处理"自我赋值"的最高效办法,但它行得通.
class Widget { ... void swap(Widget& rhs); // 交换*this和rhs的数据,详见条款29 }; Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); // 为rhs数据制作一份副本 swap(temp); // 将*this数据和上述副本的数据交换 return *this; }这个主题的另一个变奏曲利用了以下事实:(1)某 class 的copy assignment操作符可能被声明为"以by value方式接受实参";(2)以by value方式传递东西会造成一份副本(详见条款20);
Widget& Widget::operator=(Widget rhs) { // rhs是被传对象的一份副本 swap(rhs); // pass by value return *this; }它为了伶俐巧妙的修补而牺牲了清晰性,然而将"copying动作"从函数本体内移至"函数参数构造阶段"却可令编译器有时生成更高效的代码.
DerivedCustomer& DerivedCustom::operator=(const DerivedCustom& rhs) { Custom::operator=(rhs); ... return *this; }本条款所说的"复制每一个成分"现在应该很清楚了.当编写一个copying函数,确保(1)复制所有local成员变量,(2)调用所有base classes内的适当的copying函数.
版权声明:本文为博主原创文章,未经博主允许不得转载。
Effective C++——条款10条,条款11和条款12(第2章)
标签:
原文地址:http://blog.csdn.net/yiranant/article/details/48093867