1. ホーム
  2. c++

[解決済み】デストラクタで例外を投げてはいけない場合、その中のエラーはどのように処理するのでしょうか?

2022-04-02 06:22:24

質問

ほとんどの人がこう言います。 決して デストラクタで例外を発生させると、未定義の動作になります。Stroustrupは次のように指摘する。 ベクトルデストラクタは、すべての要素に対して明示的にデストラクタを呼び出します。これは、もし要素のデストラクタが投げたら、ベクターの破壊は失敗することを意味します... デストラクタから投げられる例外から保護する良い方法は本当にないので、ライブラリは要素のデストラクタが投げても何の保証もしません" (付録E3.2より) .

この記事 は、デストラクタを投げることは多かれ少なかれ問題ないと言っているようです。

デストラクタから投げると未定義の動作になる場合、デストラクタの途中で発生したエラーはどのように処理するのでしょうか?

クリーンアップ処理中にエラーが発生した場合、そのまま無視するのでしょうか?スタック上で処理できる可能性のあるエラーであっても、デストラクタ内で処理できない場合は、デストラクタの外で例外を投げるのが筋ではないでしょうか?

もちろん、この種のエラーはまれですが、あり得ることです。

解決方法は?

デストラクタから例外を投げるのは危険です。

他の例外が既に伝播している場合、アプリケーションは終了します。

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

これは基本的に煮詰まったものです。

危険なこと(つまり例外を投げる可能性のあること)は、(必ずしも直接でなく)パブリックメソッドで行うべきです。クラスのユーザーは、パブリックメソッドを使用し、潜在的な例外をキャッチすることで、これらの状況を処理できる可能性があります。

デストラクタは、これらのメソッドを呼び出してオブジェクトを終了させますが (ユーザーが明示的にそうしなかった場合)、スローされた例外はすべてキャッチされて (問題の修正を試みた後に) 取り除かれます。

つまり、事実上、ユーザーに責任を転嫁しているわけです。もしユーザーが例外を修正できる立場にあれば、適切な関数を手動で呼び出し、あらゆるエラーを処理するでしょう。オブジェクトのユーザが心配しないのであれば(オブジェクトは破棄されるので)、デストラクタに処理を任せることになります。

一例です。

std::fstream

close()メソッドは例外を投げる可能性があります。 デストラクタは、ファイルがオープンされている場合は close() を呼び出しますが、例外がデストラクタの外に伝搬しないようにします。

したがって、ファイルオブジェクトのユーザが、ファイルを閉じる際に発生する問題に対して特別な処理を行いたい場合、手動でclose()を呼び出し、あらゆる例外を処理することになります。一方、ファイルオブジェクトの使用者がファイルのクローズに関連する問題に対して特別な処理を行いたい場合は、デストラクタがその状況を処理することになります。

Scott Myersの著書「Effective C++"」にこの件に関する素晴らしい記事があります。

編集する

どうやら、"More Effective C++"にもあるようです。

項目11: デストラクタから例外が出ないようにする