第七篇:別讓異常逃離解構函式

穆晨發表於2017-01-27

前言

       解構函式的作用在於完成物件銷燬的一些“善後工作”,然而,某些不科學的設計會產生一些問題。

       本文將說明其中的一種不科學設計 - "將異常處理機制設定在解構函式中" 會產生的問題,以及解決方案。

問題描述

       首先,請看一下一段程式碼:

 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 機制,寫出健康而美妙的程式碼。

相關文章