别让异常逃离析构函数

前言

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

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

问题描述

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

 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 机制,写出健康而美妙的代码。

时间: 2024-12-25 17:53:43

别让异常逃离析构函数的相关文章

Effective C++_笔记_条款08_别让异常逃离析构函数

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) C++并不禁止析构函数吐出异常,但它不鼓励你这样做.考虑如下代码: 1: class Widget{ 2: public: 3: ... 4: ~Widget() {...} //假设这个可能吐出一个异常 5: }; 6:  7: void doSomething() 8: { 9: vector<Widget> v ; //v在这里被自动销毁 10: ...

Effective C++ Item 08-别让异常逃离析构函数

Item 08-别让异常逃离析构函数(Prevent exceptions from leaving destructors) C++并不禁止析构函数吐出异常,但它不鼓励你这样做.这是有理由的. Ex: class Widget{ public: ~Widget(){...}      //假设这个可能吐出一个异常 }; void doSomething() { std::vector<Widget> v; ...                                        

Effective C++ 条款八 别让异常逃离析构函数

class DBConn //这个class用来管理DBConnction对象 { public:   //自己设计一个新的DBConn接口 方法3 void close() { db.close(); closed = true; }     ~DBConn() //确保数据库连接总是会被关闭 { //db.close();   if (!closed) { try { db.close(); } catch() { //制作运转记录,记下对close的调用失 } } } protected:

Effective C++ Item 8 别让异常逃离析构函数

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验1: 析构函数绝对不要吐出异常.如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序. 示例: class DBConnection{ public: static DBConnection create(); //返回DBConnection对象 void close(); }; class DBConn{ //这个class用来管理D

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

举个例子: class DBConnection { public: ... static DBConnection create(); void close(); };//这个class负责数据库连接. //为了防止用户忘了close这个数据库连接,很容易想起来定义一个辅助类: class DBCon{ public: .. ~DBCon(){dbc.close();} private: DBConnection dbc; } close如果调用成功的情况下,是没问题的.但是如果调用导致异常的

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

**在调用析构函数中出现的异常有两种方法: 一是调用abort强制结束程序:** 二是吞下异常: **更好的办法是: 针对某一个可能在析构函数中出现异常的部分,为用户提供该部分函数的调用放法从而给用户处理异常的机会:** 注意:

EC笔记,第二部分:8.别让异常逃离析构函数

1.为何析构函数不应该抛出异常?    有两种情况:    1).假设析构函数中有众多语句,而第一条语句抛出异常(或者其他语句),那么抛出异常以后的语句就得不到执行.而通常我们在析构函数中写的是清理资源(或回收资源)的代码,那么部分资源就不会被回收,会造成内存泄漏或程序提前结束(abort的作用).    2).析构函数被调用的时间是在对象被销毁时,而我们很难知道(或者说没有刻意注意)对象何时被销毁,所以很难捕捉一个由析构函数抛出的异常(更别说处理了).2.两个并不高明的解决方案    1).在

[008]别让异常逃离析构函数

这章非常容易理解:因为C++并不禁止析构函数吐出异常,只是不鼓励这样做而已. 一.原因 假设我们有10个装着鸡蛋的容器,而且现在我们还想着把它在析构函数打烂. class Egg { public : ... ~Egg() { // 这里可能出错,导致蛋打不烂 } }; void foo() { vector<Egg> v // 假设v中间有10个Egg .... } // v在这里被自动销毁 如果我们在销毁10个鸡蛋的过程中,在析构第一个鸡蛋的时候,有个异常被抛出,按照销毁机制,后续的9个鸡

《Effective C++》——条款08:别让异常逃离析构函数

考虑如下代码: class Widget{ public: ... ~Widget(){...}//假设这个可能吐出一个异常 }; void doSomething() { std::vector<Widget>v; }//v在这里被销毁 当vector v被销毁,它有责任销毁其内含的所有Widgets.假设v内含十个Widgets,而在析构第一个元素期间,有个异常被抛出.其他九个Widgets还是应该被销毁,因此v应该调用它们各个析构函数.假设在调用期间,第二个Widget析构函数又抛出异常