条款8 别让异常逃离析构函数
析构函数吐出异常就是危险,总会带来“过早结束程序”或“发生不明确行为”的风险析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
假设使用一个class负责数据库连接:
class DBConnection {
public:
static DBConnection create();//此函数返回DBConnection对象,暂略参数
void close();//关闭联机;失败则抛出异常
};
class DBConn {//这个class用来管理DBConnection对象
public:
~DBConn() {
db.close();//确保数据库连接总是会被关闭
}
private:
DBConnection db;
};
{ //g开启一个block
DBConn dbc(DBConnection::create());//建立DBConnection对象并交给DBConn对象以便管理
//通过DBConn的接口,合用DBConnection对象。
//在区块结束点,DBConn对象被销毁,因而自动
//为DBConnection对象高用close
}
上述block,如果调用导致异常,DBConn析构函数会传播该异常,也就是允许它离开这个析构函数,造成的问题难以驾驭。
i、如果close抛出异常就结束程序。通过调用abort完成:
DBConn::~DBConn() {
try { db.close(); }
catch (...) {
/*制作运转记录,记下对close的调用失败*/
abort();//调用abort可以抢先制“不明确行为”于死地
}
}
于析构期间发生的错误后无法继续执行,强迫结束程序,可以阻止异常从析构函数传播出去,即调用abort可以抢先制不明确行为于死地。
ii、吞下因调用close而发生的异常:
DBConn::~DBConn() {
try { db.close(); }
catch (...) {
/*制作运转记录,记下对close的调用失败*/
}
}
吞掉异常是个坏主意,因为它压制了某些动作失败的重要信息!然而有时也好,因为即使在遭遇并忽略一个错误以后,程序必须能继续可靠的执行。
iii、一个较佳策略是重新设计DBConn接口,使其客户有机会对可能出现的问题作出反应。例如DBConn自己可以提供一个close函数,赋予客户一个机会行以处理“因该操作而发生的异常”。代码如下:
class DBConn {//这个class用来管理DBConnection对象
public:
void close() {//供客户使用的新函数
db.close();
closed = true;
}
~DBConn() {
if (!closed) {
try { db.close(); } //关闭连接(如果客户不那么做的话)
catch (...) { //如果关闭动作失败
/*制作运转记录,记下对close的调用失败*/ //记录下来并结束程序
} //或吞下异常
}
}
private:
DBConnection db;
bool closed;
};
条款9 绝不在构造和析构过程中调用virtual函数
base class构造期间virtual函数绝不会下降到derived classes阶层。取而代之的是,对象的作为就像隶属base类型一样。通俗讲,在base class构造期间,virtual函数不是virtual。
class Transaction {
public:
Transaction();
virtual void logTransaction()const = 0;
};
Transaction::Transaction() {
logTransaction();
}
class BuyTransaction :public Transaction {
public:
virtual void logTransaction()const;
};
class SellTransaction :public Transaction {
virtual void logTransaction()const;
};
BuyTransaction b;//问题在于,在bass class进行构造时,会对纯虚函数进行调用,这是严重错误的事
基函数在构造期间,如果调用纯虚函数,大多执行系统会中止程序。
避免上述问题的做法:确定构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数,而它们调用的所有函数也都服从同一约束。
解决上述问题的方法:既然无法使用virtual函数从base classes向下调用,在构造期间,可以由"令derived classes将必要的构造信息向上传递到base class构造函数"替换之而加以弥补。
class Transaction {
public:
explicit Transaction(const string logInfo);
void logTransaction()const;//现在是个non-virtual函数
};
Transaction::Transaction(const string& logInfo) {
logTransaction(logInfo);//现在是个non-virtual调用
}
class BuyTransaction :public Transaction {
public:
BuyTransaction(parameters):Transaction(createLogString(parameters)){}//将信息传给base class构造函数
private:
static string createLogString(parameters);//静态函数运用,故不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”
//正是因为“那些成员变量处于未定义状态”,所以“在base class构造和析构期间调用的virtual函数
//不可下降至derived classes”。
};
条款10 令operator=返回一个reference to *this(不仅是=,如+=、-=、*=、/=等等)
此条款举一实例说明,代码如下
class Widget {
public:
Widget& operator=(const Widget& rhs) {//返回类型是引用,因为返回对象作为左值,需要是引用返回类型才能被修改
...
return *this; //返回左侧对象
}
};
以上内容均来自Scott Meyers大师所著Effective C++ version3,如有错误地方,欢迎指正!相互学习,促进!!