C 箴言:防止因异常而离开析构函数
2008-02-23 05:40:44来源:互联网 阅读 ()
C 并不禁止从析构函数中引发异常,但是这确实妨碍了实践。至于有什么好的理由,考虑:
class Widget {
public:
...
~Widget() { ... } // assume this might emit an exception
};
void doSomething()
{
std::vector<Widget> v;
...
} // v is automatically destroyed here
当 vector v 被析构时,他有责任销毁他包含的任何 Widgets。假设 v 中有十个 Widgets,在销毁第一个的时候,抛出一个异常。其他 9个 Widgets 仍然必须被销毁(否则他们持有的任何资源将被泄漏),所以 v 应该调用他们的析构函数。但是假设在这个调用期间,第二个 Widgets 的析构函数又抛出一个异常。现在有两个异常同时在活动中,对于 C 来说这太多了。在很巧合的条件下发生这样两个同时活动的异常,程式的执行会终止或引发未定义行为。在本例中,将引发未定义行为。和此相同,使用任何标准库容器(比如,list,set),任何 TR1中的容器,甚至是个数组,都可能会引发未定义问题。并非必须是容器或数组才会陷入麻烦。程式夭折或未定义行为是析构函数引发异常的结果,即使没有使用容器或数组也会如此。C 不喜欢引发异常的析构函数。 这比较容易理解,但是假如您的析构函数需要执行一个可能失败而抛出异常的操作,该怎么办呢?例如,假设您和一个数据库连接类一起工作:
class DBConnection {
public:
...
static DBConnection create(); // function to return
// DBConnection objects; params
// omitted for simplicity
void close(); // close connection; throw an
}; // exception if closing fails
为了确保客户不会忘记调用 DBconnection 对象的 close,一个合理的主意是为 DBConnection 建立一个资源管理类,在他的析构函数中调用 close。这样的资源管理类将在以后的文章中探讨,但在这里,只要认为这样一个类的析构函数看起来像这样就足够了:
class DBConn { // class to manage DBConnection
public: // objects
...
~DBConn() // make sure database connections
{ // are always closed
db.close();
}
private:
DBConnection db;
};
他允许客户像这样编程:
{
// open a block
DBConn dbc(DBConnection::create()); // create DBConnection object
// and turn it over to a DBConn
// object to manage
... // use the DBConnection object
// via the DBConn interface
} // at end of block, the DBConn
// object is destroyed, thus
// automatically calling close on
// the DBConnection object
既然能成功地调用 close 那就好了,但是假如这个调用导致了异常,DBConn 的析构函数将散播那个异常,也就是说,他将离开析构函数。这就产生了问题,因为析构函数抛出了一个烫手的山芋。
有两个主要的方法避免这个麻烦。DBConn 的析构函数能:
终止程式 假如 close 抛出异常,调用 abort。
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
std::abort();
}
}
假如程式在析构过程遭碰到错误后不能继续运行,这就是个合理的选择。他有一个好处是:假如允许从析构函数散播异常可能会引起未定义行为,这样就能防止他发生。也就是说,调用 abort 就预先防止了未定义行为。
抑制这个异常 起因于调用 close:
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
}
}
通常,抑制异常是个不好的主意,因为他会隐瞒重要的信息——某些事情失败了!可是,有些时候,抑制异常比冒程式夭折或未定义行为的风险更可取。程式必须能够在遭碰到错误并忽略之后还能继续可靠地执行,这才能成为一个可行的选择。
这些方法都不太吸引人。他们的问题在于程式无法在第一现场对引起 close 抛出异常的条件做出回应。
一个更好的策略是设计 DBConn 的接口,以使他的客户有机会对可能会发生的问题做出回应。例如,DBConn 能够自己提供一个 close 函数,从而给客户一个机会去处理从那个操作中发出的异常。他还能保持对他的 DBConnection 是否已被关闭的跟踪,假如没有关闭就在析构函数中自己关闭他。这样能够防止连接被泄漏。假如在 DBConnection 的析构函数中调用 close 失败,无论如何,我们还能够再返回到终止或抑制。
class DBConn {
public:
...
void close() // new function for
{
// client use
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try { // close the connection
db.close(); // if the client didn’t
}
catch (...) { // if closing fails,
make log entry that call to close failed; // note that and
... // terminate or swallow
}
}
private:
DBConnection db;
bool closed;
};
将调用 close 的责任从 DBConn 的析构函数转移到 DBConn 的客户(同时在 DBConn 的析构函数中包含一个“候补”调用)可能会作为一种肆无忌惮地推卸责任的做法而刺激您。您甚至能够把他看作一个忠告(使接口易于正确使用)的违背。实际上,这都不正确。假如一个操作可能失败而抛出一个异常,而且可能是个需要处理的异常,这个异常就必须来自非析构函数。这是因为析构函数引发异常是危险的,永远都要冒着程式夭折或未定义行为的风险。在此例中,让客户调用 close 并不是强加给他们的负担,而是给他们一个时机去应付错误,否则他们将没有机会做出回应。假如他们很难找到可用到机会(或许因为他们相信不会有错误真的发生),他们可能忽略他,依靠 DBConn 的析构函数为他们调用 close。假如一个错误恰恰发生在那时——假如由 close 抛出——假如 DBConn 抑制了那个异常或终止了程式,他们将无处诉苦。毕竟,他们无处着手处理问题,他们将不再使用他。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
下一篇: C 箴言:多态基类中将析构函数声明为虚拟
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash