Effective C++读书笔记3

条款8: 别让异常逃离析构函数

C++并不禁止析构函数吐出异常,但不鼓励这样做。但如果你的析构函数必须执行一个动作,而该动作可能会在失败时抛出异常,该怎么办?

两个办法解决:

一是 如果抛出异常程序就结束,通常通过调用abort函数完成:

DBConn::~DBConn(){
	
	try{
		
		db.close();
	}catch(){
		
		std::abort();
	}
}

如果程序遭遇一个于析构期间发生的错误后无法继续执行,强迫结束程序是个合理的选择,因为他可以阻止异常从析构函数传播出去。

二是 吞下异常:

DBConn::~DBConn(){
	
	try{
		
		db.close();
	}catch(){
		
		
	}
}

一般而言,将异常吞掉是个坏主意,因为它压制了某些动作失败的重要信息!然而有时候吞下一场也比负担“草率结束程序”好。


请记住:

1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该扑捉异常,然后吞下它们(不传播)或者结束程序。

2.如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。??????


条款9: 绝不在构造和析构过程中调用virtual函数

class Transaction{                 //base class
public:
	Transaction();
	virtual void logTransaction() const = 0;
};
Transaction::Transaction(){
	//...
	logTransaction();
}

class BuyTransaction: public Transaction{
public:
	virtual void logTransaction() const;
	//...
};

class SellTransaction: public Transaction{
public:
	virtual void logTransaction() const;
	//...
};

执行以下语句:

BuyTransaction b;

基类Transaction构造函数一定会被更早调用,但其对象内的基类成分会在派生类自身成分被构造之前先构造妥当。但基类构造函数的最后一行调用virtual函数logTransaction,这时被调用的logTransaction是基类内的版本。是的,基类构造期间虚函数不会下降到派生类阶层。解决方法是:在基类内将logTransaction函数改为non-virtual,然后要求派生类构造函数传递必要信息给基类构造函数,而后那个构造函数便可以安全的调用non-virtual的logTransaction了,像这样:

class Transaction{
public:
	explicit Transaction(const std::string& logInfo);
	void logTransaction(const std::string& logInfo) const //non-virtual function
	//...
};
Transaction::Transaction(const std::string& logInfo){
	//...
	logTransaction(logInfo);
}
class BuyTransaction: public Transaction{
	
public:
	BuyTransaction(parameters): Transaction(createLogString(parameters))
	{
		
	}
private:
	static std::string createLogString(parameters);
};

由于无法使用virtual函数从基类向下调用,在构造期间,可以籍由令派生类将必要信息向上传递至基类构造函数的方法代替。


请记住:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类


条款10:令Operator=返回一个reference to *this


条款11:在operator=中处理“自我赋值”

自我赋值发生在对象被赋值给自己时.

如果遵循条款13和14的忠告,你会运用对象来管理资源,而且你可以确定所谓资源管理对象在copy发生时有正确的举措。这种情况下你的赋值操作符或许是自我赋值安全的,不需要额外操心。然而如果你尝试自行管理资源,可能会掉进“在停止使用资源之前意外释放了它”的陷阱。假设你建立一个类来保存一个指针指向一块动态分配的位图:

class Bitmap{
	
};
class Widget{
private:
	Bitmap* pb;  //point to a object allocated from heap
};

下面的operator=实现不安全:

Widget& Widget::operator=(const Widget& rhs){
	
	delete pb;    //如果此时不delete,那么在下边被赋予新值之后就再也无法delete,造成memary leak
	pb = new Bitmap(*rhs.pb);
	return *this;
}
问题是operator=内的*this和rhs有可能是同一对象,那么delete就不止delete了当前对象的bitmap,也销毁了rhs的bitmap。那么使用rhs的pb拷贝构造出来的新对象就无从谈起,返回的*this持有来了一个已经被销毁的指针。要阻止这类错误,需要在operator=的最前面加上一个“证同测试”:

Widget& Widget::operator=(const Widget& rhs){
	
	if(this == &rhs){
		return *this;
	}
	delete pb;
	pb = new Bitmap(*rhs.pb);
	return *this;
}

但这样不足以使代码具有“异常安全性”,因为new 操作可能失败,这样widget最终会持有一个指针指向一块被delete的Bitmap。解决方法是:

Widget& Widget::operator=(const Widget& rhs){
	
	Bitmap* pOrig = pb; 
	pb = new Bitmap(*rhs.pb); //因为没有先delete掉pb,所以pb可以直接接受新值而不造成内存泄漏,也不需要证同测试,因为即使相同也不会造成内存泄漏
	delete pOrig; //确保原始pb的内存会被delete,不造成内存泄漏
	return *this;
}

请记住:

1.确保当对象自我赋值时operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址,精心周到的语句顺序,以及copy-and-swap.

2.确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

条款12:复制对象时勿忘其每一个成分

如果你声明自己的拷贝函数,意思是告诉编译器你不喜欢缺省实现中的某些行为。编译器仿佛被冒犯似得,会以一种奇怪的方式回敬:当你的实现代码几乎必然出错时却不告诉你:

void logCall(const std::string& funcName);
class Customer{
	
public:
	//...
	Customer(const Customer& rhs);
	Customer& operator=(const Customer& rhs);
	//..
private:
	std::string name;
};

Customer::Customer(const Customer& rhs):name(rhs.name){
	logCall("Customrer copy constructor");
}
Customrer::Customrer::operator=(const Customrer& rhs){
	
	logCall("Customer copy assignment operator");
	name = rhs.name;
	return *this;
}

当另一个新成员变量加入后,如果程序员没有把该新成员变量在拷贝赋值和拷贝构造中处理,大多数编译器不会警告。发生继承时,可能会造成此一主题最暗中肆虐的一个潜在危机:
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& operator=(const PriorityCustomer& rhs){
	logCall("PriorityCustomer copy assignment operator");
	priority = rhs.priority;
	return *this;
}

以上两个实现复制了PriorityCustomer声明的成员变量,但每个PriorityCustomer还内含它所继承的Customer成员变量复件,而那些成员变量却未被复制。所以任何时候只要你承担起为派生类撰写拷贝函数的重大责任,必须很小心的也复制其基类成分。那些基类成分往往是private,所以无法直接访问,应该让派生类的拷贝函数调用积累的拷贝函数:
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs),priority(rhs.priority){
	
	logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=(const PriorityCustomer& rhs){
	logCall("PriorityCustomer copy assignment operator");
	Customer::operator=(rhs);
	priority = rhs.priority;
	return *this;
}

请记住:

1.拷贝函数应该确保复制对象内的所有成员变量及所有基类成分。

2.不要尝试某个拷贝函数实现另一个拷贝函数,应该将共同技能放进第三个函数中,并油两个拷贝函数共同调用。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值