条款10:令operator=返回一个reference to *this
<span style="font-size:14px;">int x, y, z; x=y=z=15;</span>
编译器解释时时这样的:
#include<iostream> using namespace std; class Widget { public: Widget() { cout<<"Default Ctor"<<endl; } Widget(const Widget& rhs) { cout<<"Copy Ctor"<<endl; } Widget& operator=(const Widget& rhs) { cout<<"operator="<<endl; return *this; } }; int main() { Widget a,b,c; a=b=c; return 0; }
这样输出为:
Default Ctor
Default Ctor
operator=
operator=
Default Ctor
Default Ctor
operator=
Copy Ctor
operator=
Copy Ctor
条款11:在operator=中实现“自我赋值”
class Widget { public: Widget& operator=(const Widget& rhs) { delete p; p=new int(ths.p); return *this; } int *p; };
如果上面代码自我赋值,在使用指针p之前已经将其释放掉了。
class Widget { public: Widget& operator=(const Widget& rhs) { if(this==&rhs)//证同测试 return *this; delete p; p=new int(rhs.p); return *this; } int *p; };
这个版本的operator=可以解决自我赋值的问题。但是还有个问题:异常安全。如果delete p成功,而p=new int(rhs.)失败会发生什么?
class Widget { public: Widget& operator=(const Widget& rhs) { int tmp=p;//记录原先内存 p=new int(rhs.p); delete tmp;//释放原先内存 return *this; } int *p; };
在实现异常安全的同时,其实也获取了自我赋值的安全。如果p=new int(ths.p)发生异常,后面的delete tmp就不会执行。
class Widget { public: void swap(const Widget& rhs);//交换rhs和this Widget& operator=(const Widget& rhs) { Widget tmp(rhs);//赋值一份数据 swap(tmp)//交换 return *this;//临时变量会自动销毁 } int *p; };
如果赋值操作符参数是值传递,那么就不需要新建临时变量,直接使用函数参数即可。
class Widget { public: void swap(const Widget& rhs);//交换rhs和this Widget& operator=(const Widget rhs) { swap(rhs) return *this; } int *p; };
这个做法代码可读性比较差,但是将“copying动作”从函数体内移到“函数参数构造阶段”,编译器有时会生成效率更高的代码(by moving the copying operation from the body of the function to construction of the parameter, it's fact that compiler can sometimes generate more efficient code.
条款12:复制对象时勿忘其每一部分
class Cutsomer { public: Cutsomer() { name="nobody"; } Cutsomer(const Cutsomer& rhs) :name(rhs.name) { cout<<"Customer Copy Ctor"<<endl; } Cutsomer& operator=(const Cutsomer& rhs) { cout<<"assign operator"<<endl; name=rhs.name; return *this; } private: string name; };
这样的copying函数没有问题,但是如果再给类添加变量,例如添加一个电话号码
class Cutsomer { …… private: string name; string telphone; };
这时copying函数不做更改,即便是在最高警告级别,编译器也不会报错,但是我们的确少拷贝了内容。
class PriorityCustomer:public Cutsomer { public: PriorityCustomer() { cout<<"PriorityCustomer Ctor"<<endl; } PriorityCustomer(const PriorityCustomer& rhs) :priority(rhs.priority) { cout<<"PriorityCustomer Copy Ctor"<<endl; } PriorityCustomer& operator=(const PriorityCustomer& rhs) { cout<<"PriorityCustomer assign operator"<<endl; priority=rhs.priority; return *this; } private: int priority; };
在PriorityCustomer的copying函数中,只是复制了PriorityCustomer部分的内容,基类内容被忽略了。那么其基类内容部分怎么初始化的呢?
PriorityCustomer(const PriorityCustomer& rhs) :Cutsomer(rhs),priority(rhs.priority) { cout<<"PriorityCustomer Copy Ctor"<<endl; } PriorityCustomer& operator=(const PriorityCustomer& rhs) { cout<<"PriorityCustomer assign operator"<<endl; Cutsomer::operator=(rhs); priority=rhs.priority; return *this; }
可以发现复制构造函数和赋值操作符有类似的代码。但是者两个函数不能相互调用。复制构造函数是构造一个不存在的对象,而赋值操作符是给一个存在的对象重新赋值。消除重复代码的方法编写一个private方法,例如void Init()。在这个函数中操作重复代码。