大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C++技巧 > C++技巧:防止因异常而离开析构函数

C++技巧:防止因异常而离开析构函数

关键词:C++析构函数异常  阅读(500) 赞(13)

[摘要]本文主要是对C++技巧:防止因异常而离开析构函数的讲解,希望对您学习C++有所帮助!

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 抑制了那个异常或许终止了顺序,他们将无处诉苦。毕竟,他们无处着手处置效果,他们将不再运用它。

Things to Remember

·析构函数应该永不引发异常。假定析构函数调用了可以抛出异常的函数,析构函数应该捕捉任何异常,然后抑制它们或许终止顺序。

·假定类客户需求能对一个操作抛出的异常做出回应,则那个类应该提供一个常规的(非析构函数)函数来完成这个操作。



相关评论