码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2014-11-11 12:43:09      阅读:161      评论:0      收藏:0      [点我收藏+]

标签:effective c++   学习笔记   条款08   

***************************************转载请注明出处: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********************************************

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

标签:effective c++   学习笔记   条款08   

原文地址:http://blog.csdn.net/lttree/article/details/41007475

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!