***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
二、Constructors,Destructors and Assignment Operators
Rule 08:Prevent exceptions from leaving destructors
规则08:别让异常逃离析构函数
1.再来谈一个C++讨厌的东西
C++并不禁止析构函数吐出异常,但它不鼓励你这样做。
首先,看下面这个例子:
class Widget { public: ... ~Widget( ) { ... } // 假设这里可能吐出异常 }; void doSomething() { std::vector<Widget> v; ... } // v在这里被销毁
当vector v 被销毁,它就会将所有Widget销毁。加入v有10个Widget,在析构第一个Widget时,有一个异常被抛出,Ok,还有九个要被销毁,在销毁第二个时,又有一个异常抛出。额。。。这下对于C++来说就有些多了。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确的行为。So,C++不喜欢析构函数吐出异常。
2.即使知道C++讨厌,但我们无法避免要面对它
如果析构函数必须执行某个动作,但这个动作可能导致异常,怎么办呢?
依旧用书上的例子,用一个类来负责数据库连接:
class DBConnection { public: ... static DBConnection create(); void close(); };
但是为了确保客户不忘记在 DBConnection 对象上调用close(),一个合理的想法是创建一个用来管理DBConnection资源的class并在其析构函数中调用close,如同下面这种:
class DBConn { public: ... ~DBConn() { db.close(); } private: DBConnection db; };
但是这样就会允许用户写出这样的代码:
{ DBConn dbc( DBConnection::create() ); ... }<span style="white-space:pre"> </span>// 在块结束时DBConn对象被销毁,因为自动为DBConnection对象调用close
这就是个很大的问题了,只要调用成功,那就一切美好,如果调用导致异常,则会导致异常的传播,这就相当糟糕。
对于这个情况有两个方案来搞定:
<1>plan01:
如果 close 抛出异常就结束程序。通常就通过调用abort完成:
DBConn::~DBConn( ) { try{ db.close(); } catch( ... ) {<span style="font-family: Arial, Helvetica, sans-serif;"> </span>
<pre name="code" class="cpp"> 制作运转记录,记下对close的调用失败;
std::abort(); }}
对于出现异常,就强迫结束程序。毕竟它可以阻止异常从析构函数传播出去。
<2>plan02:
吞下因调用close而发生的异常
DBConn::~DBConn( ) { try { db.close(); } catch ( ... ) { 制作运转记录,记下对close的调用失败; } }
一般的来说,吞下异常是个坏主意,因为它压制了“某些动作失败”的重要信息!然而有时吞下异常也比 “ 草率的结束程序 ” 或者 “ 不明确的行为带来的风险” 好。
3.更好的方法?
上面两种方法都是因为 无法对 导致close抛出异常 的情况作出反应。
所以,更好的办法,就是 重新设计 DBConn 的接口,比如:
class DBConn { public: ... void close()<span style="white-space:pre"> </span>// 供用户使用的新函数 { db.close(); closed = true; } ~DBConn() { if( !closed ) { try {<span style="white-space:pre"> </span>// 如果用户没有关闭连接,则执行关闭连接 db.close(); } catch ( ... ) {<span style="white-space:pre"> </span>// 如果关闭动作失败 制作运转记录,记下对close的调用失败; .... } } } private: DBConnection db; bool closed; };
这个方案很易于理解,设置一个bool类型变量检测,如果用户直到结束,也没有将连接关闭,则替用户关闭连接,否则,如果用户关闭了连接,我们无需在做任何事情。在关闭连接时,若发生异常抛出,那就只能回到 吞下异常 这条老路了,这个是怎么也避免不了了。
这个方案的优点,把调用close责任给到用户手上,但是还设置了一个双保险,让程序更加安全。
4.请记住:
★ 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(让它们不能传播出去)或者 结束程序。
★ 如果客户需要对某个操作函数运行期间抛出异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************