条款8:别让异常逃离析构函数

目录

1.前言

2.分析实例

2.1析构函数抛出异常的实例

2.2解决方法


1.前言

C++并不禁止析构函数吐出异常,但是不鼓励出现这种情况。例如以下代码:

class Widget{
    public:
        ...
        ~Widget()            
        {
            ....//假设这个可能出现异常
        }

};
void doSomething()
{
    std::vector<Widget> v;
       .....//v这里被自动销毁
}

在上述代码中,当vector v被销毁时,它有责任销毁其内含的所有Widgets。假设v内含十个Widgets,而在析构第一个元素期间,有个异常被抛出。其他9个Widgets还是应该被销毁(否则它们保存的任何资源都可能发生资源泄露),因此v会调用它们各个析构函数。但假设在那些调用期间,第二个Widget析构函数又抛出异常。现在有两个同时作用的异常,这对C++而言太多了,因为在两个异常同时存在的情况下,程序不是结束执行,就是出现不明确行为。

2.分析实例

2.1析构函数抛出异常的实例

假设析构函数必须执行一个动作,而该动作可能会在失败时抛出异常,该怎么办?假设存在这样一个class负责数据库连接:

class DBConnection{
    public:
        ....
        static DBConnection create();//该函数返回DBConnection对象
        .....
        void close();关闭联机;失败时会抛出异常


}

为确保不忘记在DBConnection对象身上调用close(),一种方法是创建一个用来管理DBConnection资源的class,并在其析构中调用close。即:

class DBConn{
    public:
        ...
        ~DBConn()
        {
            db.close();//确保数据库连接总是会被关闭
        }

       private:
            DBConnection db;

}

于是乎,存在如下逻辑的代码:

class DB{
    
    DBConn dbc(DBConnection::create());//开启一个区块block,建立DBConnection对象并交给DBConn对象以便管理,通过DBConn的接口,使用DBConnection对象。在区块结束点,DBConn对象被销毁,因而自动被DBConnection对象调用close

}

当close调用成功,everything is fine。但是当调用异常,DBConn析构函数会传播该异常,也就是允许它离开这个析构函数。这种情况会导致出现比较麻烦的问题。

2.2解决方法

这里存在3种方法来避免这一问题,DBConn的析构函数:

1.如果close抛出异常就结束程序。通常调用abort来完成;

DBConn::~DBConn()
{
    try{db.close();}
    catch(...)
    {
        //制作运行记录,记下对close的失败调用;
        std::abort();
    }
}

之所以它作为一个解决问题的方法,主要为它可以阻止异常从析构函数传播出去,即调用abort阻止出现不明确错误的行为;

2.吞下因调用close而发生的异常

DBConn::~DBConn()
{
    try{db.close();}
    catch(...)
    {
        //制作运转记录,记下对close的失败调用
    }

}

3.重新设计DBConn接口,使其有机会对可能出现的问题作出反应。如以下代码逻辑:

class DBConn{
    public:
        ...
        void close()
        {
            db.close();
            closed=true;
        }
        ~DBConn()
        {
            if(!closed)
            {
                try{//关闭连接
                    db.close();
                   }
                catch(...)//如果关闭动作失败。记录下来并结束程序或吞下异常
                {
                    //制作运转记录,记下对close的失败调用。
                }
            }
        }

        private:
            DBConnection db;
            bool closed;
};

在上述代码中,把调用close的责任从DBConn析构函数手上转移到DBConn手上。这是因为如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。因为析构函数突出异常就是危险,总会带来过早的结束程序或者发生不明确行为的风险。比如上述代码中,有其本身自己调用close并不会对他们带来负担,而是给它们一个处理错误的机会,否则它们没机会相应。如果它们认为这个机会没用,可以忽略它,依靠DBConn析构函数去调用close。即使有错误发生-即close抛出异常-并且DBConn吞下该异常或结束程序,也不会抱怨,毕竟它们本来有机会第一手处理问题,却选择放弃。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值