iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
自我挑戰組

Effective C++ 讀書筆記系列 第 12

[Day 12] Prevent exceptions from leaving destructors

  • 分享至 

  • xImage
  •  

前言

今天這則守則非常單純好理解,就來輕鬆一下吧!

別讓destructor丟exception

這個守則是:

Prevent exceptions from leaving destructors

簡單來說就是不要讓destructor throw exception。為什麼呢?因為destructor他會去清理這個物件該被清掉的東西,如果清到一半throw了一個exception,但destructor又需要繼續清理,此時如果又來一個exception,C++對於這種情形未定義,要馬直接terminate,要馬會產生未定義的行為。

好吧!那道理我都懂,現在該怎麼辦呢?例如書中舉了一個例子:

class DBConnection{
public:
    static DBConnection create();
    void close();
};

為了要讓所有call到DBConnection的東西都記得要close,我們用了一個DBConnclass來管理DBConnection這個object,這個後續的守則會再提到(資源管理的部分)。這個DBConn我們先不管他怎麼做的,但總之就是讓他desctructor的時候會執行db.close,好確保這個DBConnection都會被close:

class DBConn{
public:
    ~DBConn()
    {
        db.close();
    }
}
private:
    DBConnection db;

別人就會這樣使用他:

{
    DBConn dbc(DBConnection::create());
}

此時如果dbc在desctruct的時候,裡面的db.close()有exception產生,而這個destructor如果再把這個exception往外丟,就如同前面描述的,就有麻煩了。
書中說明現在有兩種解法:

  1. 直接終止程式
    例如這樣:
DBConn::~DBConn()
{
    try { db.close(); }
    catch (...)
    {
        // make log entry that call to close failed;
        std::abort();
    }
}

這很直接地避免了exception被從destructor丟出來的問題,因為直接在丟出來之前就終止程式了。換言之,就是搶先在未定義行為發生前終止程式。簡單來說,就是遇到exception就不繼續清啦!不知道繼續清你會怎樣,乾脆大家都停一停,不要做了。

  1. 處理掉exception
    例如這樣:
DBConn::~DBConn()
{
    try { db.close(); }
    catch (...)
    {
        // make log entry that call to close failed;
    }
}

跟前面比起來就是不abort,這有什麼好處?一樣就不繼續清啦!也是因為繼續清可能會有問題,那我們就清到這邊ok。不過這樣就要有把握程式接下來還能繼續跑,而且這樣等於有些東西沒被清掉。

提前處理它?

那有沒有更好的做法呢?預防勝於治療,就是讓這個exception最好不要到destructor本身才被丟出來,讓它有機會提前去爆發應對。例如這樣:

class DBConn{
public:
    void close()
    {
        db.close();
        closed = true;
    }
}
    ~DBConn()
    {
        if(!closed)
        {
            try
            {
                db.close();
            }
            catch (...)
            {
                // make log entry that call to close failed;
            }
        }
    }
private:
    DBConnection db;
    bool closed;

這做了什麼?說白了就是讓close不再由destructor去管理,db要自己記得去關。雖然desctructor一樣要確保沒close的話也要close掉,但這只是以防萬一。所以若有exception的話應該早在前面就要爆發並處理掉。當然destructor那邊也要做好如果沒close掉的話要去中止或吞掉。

這有道理嗎?道理就是會引發exception的行為不該在destructor發生。如果有可能那就要先處理掉,挪出destructor。

總結

貼心重點提醒:

  • Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program.
  • If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular(i.e., non-destructor) function that performs the operation.

簡單來說desctructor預期noexcept(since C++11),不預期有exception在desctructor發生。

補充資料


上一篇
[Day 11] Declare destructors virtual in polymorphic base classes
下一篇
[Day 13] Never call virtual functions during construction and destruction
系列文
Effective C++ 讀書筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言