您的位置:首页 > 编程语言 > C语言/C++

《Effective C++ 》学习笔记——条款08

2014-11-11 12:04 211 查看
***************************************转载请注明出处: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********************************************
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息