Effective C++ 条款11、12

条款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,如有错误地方,欢迎指正!相互学习,促进!!

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80397229
今日推荐