• 第七篇:别让异常逃离析构函数


    前言

           析构函数的作用在于完成对象销毁的一些“善后工作”,然而,某些不科学的设计会产生一些问题。

           本文将说明其中的一种不科学设计 - "将异常处理机制设定在析构函数中" 会产生的问题,以及解决方案。

    问题描述

           首先,请看一下一段代码:

     1 class Widget {
     2 public:
     3     //......
     4     ~Widget() {    // 假定这个析构函数可能会吐出异常
     5         //......
     6     }
     7     //......
     8 };
     9 
    10 void doSomething()
    11 {
    12     //......
    13     std::vector<Widget> v;
    14     //......
    15 }

           当 doSomething 函数执行完毕的时候,会开始析构容器 v 中存放的各个 Widget 对象。

           然而,该对象析构函数可能会吐出异常。在这种情况下,将会吐出容器中对象个数的异常,这就会导致程序运行的不确定性。

    实例分析

           如果在对象结束的时候,必须执行一个可能抛出异常的动作,那该怎么办呢?

           下面通过一个实际应用中的例子,来说明如何解决这个问题。

           首先,定义两个类分别负责数据库连接以及该类资源的管理:

     1 // 该类负责数据库的连接
     2 class DBConnection {
     3 public:
     4     //......
     5     static DBConnection create();    // 建立一个DBConnection对象
     6     //......
     7     void close();
     8     //......
     9 };
    10 
    11 // 该类负责类DBConnection的资源管理
    12 class DBConn {
    13 public:
    14     //......
    15     ~DBConn() {    // 析构函数确保数据库连接总是会被关闭
    16         db.close();
    17     }
    18     //......
    19 private:
    20     //......
    21     DBConnection db;
    22     //......
    23 };

           这样,客户可以写出如下代码以管理数据库连接:

    1 {
    2     //......
    3     DBConn dbc(DBConnection::create());
    4     //......
    5 }

           该段代码结束时,DBConn 对象可能会抛出异常,而一旦异常抛出,就有可能产生问题描述中所说的那种问题现象。

           正确的做法是:将 close() 的执行移交给用户,然后再在析构函数中try and catch,请看解决方案代码:

     1 // 修改后的DBConn类实现
     2 class DBConn {
     3 public:
     4     //......
     5     void close() {    // 要求用户自己关闭数据库对象
     6         db.close();
     7         closed = true;
     8     }
     9     //......
    10     ~DBConn() {
    11         if (!closed) {    // 如果用户忘记了这么做,就采用 try catch 机制吞下异常。
    12             try {
    13                 db.close();
    14             }
    15             catch (...) {
    16                 // 记录此次 close 失败
    17                 //......
    18             }
    19         }
    20     }
    21     //......
    22 private:
    23     //......
    24     DBConnection db;
    25     bool closed;    // 增设此变量用以判断用户是否已经自行调用 close(),用户也可根据此变量判断 close() 是否顺利执行并作出相应的异常处理。
    26     //......
    27 };

           这种做法实质是种“双保险”:首先要求用户自行调用 close()。如果用户忘了,也会有 try catch吞掉异常。

           最好的情况是用户按照类使用手册的要求自行调用 close()。用户要是实在粗心大意,忘记了,那就只能让 catch 去吞掉异常,在这种情况下,客户没有资格抱怨,说直接吞掉异常不好。

    小结

           要多多结合实际应用,合理使用 C++ 提供的 try 和 catch 机制,写出健康而美妙的代码。

  • 相关阅读:
    九度OJ 1168:字符串的查找删除 (查找)
    九度OJ 1167:数组排序 (排序)
    九度OJ 1166:迭代求立方根 (迭代)
    九度OJ 1165:字符串匹配 (模式匹配)
    九度OJ 1164:旋转矩阵 (矩阵运算)
    九度OJ 1163:素数 (素数)
    九度OJ 1162:I Wanna Go Home(我想回家) (最短路径)
    九度OJ 1161:Repeater(复制器) (递归)
    九度OJ 1160:放苹果 (DFS)
    Sub-process /usr/bin/dpkg returned an error code (1)错误解决办法
  • 原文地址:https://www.cnblogs.com/muchen/p/6353751.html
Copyright © 2020-2023  润新知