条款11 在operator=中处理“自我赋值”
"自我赋值"发生在对象赋值给自已时(这种情况是时常发生,所以并不可笑,很正常)
class Bitmap{...}; class Widget{ public: Widget& operator=(const Widget& rhs); private: Bitmap* pb; }; Widget w; w = w; //又如 a[i] = a[j];//当i=j时,就是自赋值 *px = *py;//px与py指向同一对象时,就是自赋值一份不安全的operator=实现版本
Widget& Widget::operator=(const Widget& rhs) { delete pb;//万一pb指向的对就是rhs,麻烦就大了 pb = new Bitmap(*rhs.pb); return *this; }纠正上述未考虑自赋值情况,传统做法就是在operator=最前面利用“证同测试”达到“自我赋值”的检验目的
Widget& Widget::operator=(const Widget& rhs) { if (*this == rhs)//证同测试 return *this; delete pb; pb = new Bitmap(*rhs.pb); return *this; }
上述考虑自赋值情况的代码不具备异常安全,也就是当"new Bitmap"导致异常时,此时pb指针将指向一块被删除的Bitmap(很灾明显灾难发生啦)。
解决方法:让operator具备“异常安全性”往往自动获得“自我赋值安全”的回报。于是重点便放在实现“异常安全性”上即可。细细体会下面的代码,即使异常发生在new Bitmap内,指针pb仍然所指向正确。
Widget& Widget::operator=(const Widget& rhs) { Bitmap* pOrig = pb; pb = new Bitmap(*rhs.pb); delete pOrig; return *this; }
条款12 复制对象时勿忘其每一个成分
编译器会在必要的时候为classes创建copying函数,其行为:将被拷对象的所有成员变量都做一份拷贝。
如果声明自己的copying函数,于是当实现的代码几乎必然出错时却不通知。如下面举例:
void logCall(const string& funcName); class Customer { public: Customer(const Customer& rhs); Customer& operator=(const Customer& rhs); private: string name; }; Customer::Customer(const Customer& rhs):name(rhs.name){ logCall("Customer copy constructor"); } Customer& Customer::operator=(const Customer& rhs) { logCall("Customer copy assignment operator"); name = rhs.name; return *this; }
如果上述Customer类中添加了一个私有成员,则所有的copying函数都要修改。
一旦发生继承,可能会造成此一主题最暗中肆虐的一个潜藏危机,如下代码分析:
class PriorityCustomer :public Customer { public: PriorityCustomer(const PriorityCustomer& rhs); PriorityCustomer& operator=(const PriorityCustomer& rhs); private: int priority; }; PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):priority(rhs.priority) {//灾难产生,继承的成员变量未被拷贝 logCall("PriorityCustomer copy constructor"); } PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs) { logCall("PriorityCustomer copy assignment operator"); priority = rhs.priority; return *this; }
所以只要承担为derived class撰写copying函数的重责大任,必须很小心地也复制其base class成分。(针对上述copying构造和赋值函数,可以通过derived class的copying函数调用相应的base class copying构造函数),见代码分析:
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) : priority(rhs.priority),Customer(rhs) { logCall("PriorityCustomer copy constructor"); } PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs) { logCall("PriorityCustomer copy assignment operator"); Customer::operator=(rhs); priority = rhs.priority; return *this; }
本条款切需遵循的一条准则:编写一个copying函数时,请确保i、复制所有local成员变量,ii、调用所有base classes内的适当的copying函数。(绝对不要令copying赋值操作符调用copy构造函数,或之向调用,应该各管各的,各施其职。)
以上内容均来自Scott Meyers大师所著Effective C++ version3,如有错误地方,欢迎指正!相互学习,促进!!