デストラクタから例外を投げる


257

ほとんどの人が言う未定義の動作結果そう-デストラクタの外に例外をスローしません。Stroustrupは、「ベクターデストラクタはすべての要素に対して明示的にデストラクタを呼び出す。これは、要素デストラクタがスローすると、ベクトルの破棄が失敗することを意味する...デストラクタからスローされる例外から保護するための適切な方法は実際にはないため、ライブラリ要素デストラクタがスローした場合の保証はありません」(付録E3.2から)

この記事はそうではないようです-投げるデストラクタは多かれ少なかれ大丈夫です。

だから私の質問はこれです-デストラクタからスローすると未定義の動作が発生する場合、デストラクタ中に発生したエラーをどのように処理しますか?

クリーンアップ操作中にエラーが発生した場合、それを無視しますか?スタックを処理できる可能性があるが、デストラクタで正しく処理できないエラーの場合、デストラクタから例外をスローするのは理にかなっていますか?

明らかにこれらの種類のエラーはまれですが、可能です。


36
「2つの例外を一度に」はストックアンサーですが、それは本当の理由ではありません。本当の理由は、関数の事後条件が満たされない場合にのみ、例外がスローされる必要があることです。デストラクタの事後条件は、オブジェクトが存在しないことです。これは起こり得ないことです。したがって、オブジェクトがスコープから外れる前に、障害が発生しやすい寿命末期の操作を個別のメソッドとして呼び出す必要があります(通常、賢明な関数には1つの成功パスしかありません)。
スプラフ

29
@spraff:あなたが言ったことは「RAIIを捨てる」ことを意味することを知っていますか?
Kos

16
@spraff:「オブジェクトがスコープから外れる前に別のメソッド」を呼び出す必要がある(書いたとおり)と、実際にはRAIIが破棄されます。そのようなオブジェクトを使用するコードは、デストラクタが呼び出される前にそのようなメソッドが呼び出されることを確認する必要があります。最後に、このアイデアはまったく役に立ちません。
Frunsi 2012

8
@Frunsiいいえ、この問題は、デストラクタが単なるリソースの解放を超えて何かをしようとしているという事実から生じているためです。「私はいつもXYZをやりたい」と言いたくて、これをそのようなロジックをデストラクタに入れるための議論だと考えたくなります。いいえ、遅延しないでくださいxyz()。デストラクタを作成して、RAII以外のロジックをクリーンな状態に保ちます。
スプラフ

6
@Frunsiたとえば、何かをファイルにコミットすること、トランザクションを表すクラスのデストラクタで必ずしも実行できるわけではありません。コミットが失敗した場合、トランザクションに関連するすべてのコードがスコープ外になったときに、それを処理するには遅すぎます。commit()メソッドが呼び出されない限り、デストラクタはトランザクションを破棄する必要があります。
ニコラスウィルソン

回答:


198

デストラクタから例外をスローすることは危険です。
別の例外がすでに伝播している場合、アプリケーションは終了します。

#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()を呼び出し、例外を処理します。一方、彼らが気にしない場合、デストラクタは状況を処理するために残されます。

スコットマイヤーズの著書「Effective C ++」には、この主題に関する優れた記事があります。

編集:

どうやら「より効果的なC ++」の
項目11:例外がデストラクタを離れないようにする


5
「アプリケーションが終了する可能性を気にしない限り、おそらくエラーを飲み込むべきです。」-これは、ルールではなく例外(しゃれを許す)でなければなりません-つまり、すぐに失敗します。
エリックフォーブス

15
同意しません。プログラムを終了すると、スタックの巻き戻しが停止します。デストラクタが呼び出されることはありません。開いているリソースは開いたままになります。例外を飲み込むことが望ましい選択肢だと思います。
マーティンヨーク

20
OSは所有者であるリソースをクリーンアップできます。メモリ、FileHandlesなど。複雑なリソースはどうですか:DB接続。開いたISSへのそのアップリンク(自動的にクローズ接続を送信する予定ですか)?NASAはあなたに接続をきれいに閉じて欲しいと確信しています!
マーティンヨーク、

7
アプリケーションがアボートして「すぐに失敗する」場合、そもそも例外をスローするべきではありません。スタックに制御を渡すことによって失敗する場合は、プログラムが異常終了する可能性のある方法でそうするべきではありません。どちらかを選択しないでください。
トム

2
@LokiAstari宇宙船との通信に使用しているトランスポートプロトコルは、切断された接続を処理できませんか?OK ...
doug65536

54

このデストラクタは「スタックの巻き戻し」の一部として呼び出される可能性があるため、デストラクタからスローするとクラッシュする可能性があります。スタックの巻き戻しは、例外がスローされたときに実行される手順です。この手順では、「試行」以降、例外がスローされるまでスタックにプッシュされたすべてのオブジェクトが終了します->それらのデストラクタが呼び出されます。この手順では、一度に2つの例外を処理することができないため、別の例外のスローは許可されません。したがって、これにより、abort()の呼び出しが引き起こされ、プログラムがクラッシュし、コントロールがOSに戻ります。


1
上記の状況でabort()がどのように呼び出されたかを詳しく説明していただけますか?実行の制御が依然としてC ++コンパイラーを使用していたことを意味します
Krishna Oza 2014年

1
@Krishna_Oza:非常にシンプル:エラーがスローされるたびに、エラーを発生させるコードは、ランタイムシステムがスタックの巻き戻しの処理中であることを示すビットをチェックします(つまり、他のいくつかを処理していますが、まだブロックthrowを見つけcatchていません)。その場合、(新しい)例外を発生させる(またはスタックの巻き戻しを続行する)代わりにstd::terminate(ではないabort)が呼び出されます。
Marc van Leeuwen、

53

特定のケースについて一般的なアドバイスを盲目的に行うのではなく、ここで区別する必要があります。

以下で、オブジェクトのコンテナーの問題と、コンテナー内のオブジェクトの複数のオブジェクトに直面した場合の対処法を無視していることに注意してください。(一部のオブジェクトはコンテナーに入れるのに適していないため、部分的に無視できます。)

クラス全体を2つの型に分割すると、問題全体が簡単に考えられるようになります。クラスdtorには、2つの異なる責任があります。

  • (R)リリースセマンティクス(別名メモリを解放)
  • (C)コミットセマンティクス(別名ファイルをディスクにフラッシュ

このように質問を見ると、(R)セマンティクスでは、dtorから例外が発生することはないはずであると考えることができます。エラーチェックなども提供します。void free(void* p);

私たちは:正常にのデータをフラッシュするか(「スコープが守られて」)へのニーズデストラクタにコミットしないデータベース接続が異なる種類のものであること、ファイルオブジェクトのような(C)セマンティクスを持つオブジェクト、できるエラーについて何かを(上アプリケーションレベル)、そして何も起こらなかったかのように継続するべきではありません。

RAIIルートをたどり、d'torに(C)セマンティクスを持つオブジェクトを許可する場合、そのようなd'torがスローできる奇妙なケースも許可する必要があると思います。そのため、そのようなオブジェクトをコンテナーに入れてはならずterminate()、別の例外がアクティブなときにcommit-dtorがスローした場合でも、プログラムは引き続きそれを行うことができます。


エラーに関しては、(コミット/ロールバックセマンティクス)と例外を処理して、良い話が1でありアンドレイアレキエラーがC ++ /宣言制御フローで処理(で開催されたNDC 2014

詳細については、FollyライブラリUncaughtExceptionCounterScopeGuardツールにを実装する方法を説明しています。

他の人も同様の考えを持っていたことに注意する必要あります。)

トークはドトールから投げることに焦点を合わせていないが、それは今日使われ、ドトールから投げるとき問題を取り除くために使用できるツールを示している。

では、将来、そこにあり、このためのstd特徴で参照N3614をそしてそれについての議論

Upd '17:これのC ++ 17 std機能はstd::uncaught_exceptionsafaiktです。cpprefの記事をすぐに引用します。

ノート

int-returning uncaught_exceptionsが使用される例は... ...最初にガードオブジェクトを作成し、キャッチされなかった例外の数をコンストラクターに記録します。foo()がスローしない限り、出力はガードオブジェクトのデストラクタによって実行されます(この場合、デストラクタで捕捉されなかった例外の数は、コンストラクタが監視したものよりも大きくなります)。


6
非常に同意します。そして、セマンティック(Ro)ロールバックセマンティクスをもう1つ追加します。スコープガードで一般的に使用されます。ON_SCOPE_EXITマクロを定義した私のプロジェクトの場合と同様です。ロールバックセマンティクスの場合は、意味のあることがここで発生する可能性があるということです。だから私たちは本当に失敗を無視すべきではありません。
Weipeng L 2013

デストラクタでコミットセマンティクスを使用する唯一の理由は、C ++がをサポートしていないためだと思いfinallyます。
user541686

@Mehrdadは:finally あるデストラクタ。何があっても常に呼び出されます。finallyの構文近似については、さまざまなscope_guard実装を参照してください。今日では、dtorがスローすることを許可されているかどうかを検出するための機構(標準でも、C ++ 14ですか?)を備えているため、完全に安全にすることもできます。
Martin Ba

1
@MartinBa:(R)と(C)が異なるというあなたの考えに同意していたので、あなたは私のコメントのポイントを逃したと思います。dtorは本質的に(R)finallyのツールであり、本質的に(C)のツールであると言っていました。中に互いの上に例外をスローするために正当な理由だ考える:あなたはなぜ表示されない場合はfinally、ブロック、なぜ同じことがありませんデストラクタのために。(ある意味で、これはデータと制御の問題です。デストラクタはデータを解放するfinallyためのもので、制御を解放するためのものです。それらは異なります。C++がそれらを結合するのは残念です。)
user541686

1
@Mehrdad:ここで長くなりすぎます。必要に応じて、programmers.stackexchange.com / questions / 304067 /…で引数を作成できます 。ありがとう。
Martin Ba

21

デストラクタからスローすることについて自問する本当の質問は、「呼び出し側がこれで何ができるか」です。デストラクタからスローすることによって生じる危険を相殺する例外を除いて、実際にできることはありますか?

Fooオブジェクトを破棄し、Fooデストラクタが例外を投げた場合、それで合理的に何ができるでしょうか?ログに記録することも、無視することもできます。それで全部です。Fooオブジェクトが既になくなっているので、「修正」できません。最良の場合、例外をログに記録し、何も起こらなかったかのように続行します(またはプログラムを終了します)。これは、デストラクタからスローすることによって未定義の動作を引き起こす可能性がある本当に価値がありますか?


11
ただ気づいた... dtorから投げること決して未定義の行動ではありません。確かに、terminate()を呼び出す可能性がありますが、それは非常に明確に指定された動作です。
マーティン・バ

4
std::ofstreamのデストラクタは、ファイルをフラッシュしてから閉じます。フラッシュ中にディスクが一杯になるエラーが発生する可能性があります。これを使用すると、ディスクに空き容量がないことを示すエラーダイアログをユーザーに表示できます。
アンディ

13

危険ですが、読みやすさ/コードの理解の観点からは意味がありません。

あなたが尋ねなければならないのはこの状況です

int foo()
{
   Object o;
   // As foo exits, o's destructor is called
}

何が例外をキャッチすべきですか?fooの呼び出し元は?またはfooはそれを処理する必要がありますか?fooの呼び出し側がfooの内部にあるオブジェクトを気にする必要があるのはなぜですか?言語がこれを意味のあるものとして定義する方法はあるかもしれませんが、それは判読できず、理解するのが難しいでしょう。

さらに重要なのは、オブジェクトのメモリはどこに行くのでしょうか。オブジェクトが所有するメモリはどこに行きますか?それはまだ割り当てられていますか(デストラクタが失敗したためと思われます)?オブジェクトがスタックスペースにあったので、それは明らかに関係なく消えたと考えてください。

次に、このケースを検討してください

class Object
{ 
   Object2 obj2;
   Object3* obj3;
   virtual ~Object()
   {
       // What should happen when this fails? How would I actually destroy this?
       delete obj3;

       // obj 2 fails to destruct when it goes out of scope, now what!?!?
       // should the exception propogate? 
   } 
};

obj3の削除が失敗した場合、失敗しないことが保証されている方法で実際に削除するにはどうすればよいですか?その私の記憶は酷い!

最初のコードスニペットでは、Object3がヒープ上にあるときにオブジェクトがスタック上にあるため、オブジェクトは自動的に削除されます。Object3へのポインタがなくなったので、あなたは一種のSOLです。メモリリークがあります。

安全な方法の1つは次のとおりです。

class Socket
{
    virtual ~Socket()
    {
      try 
      {
           Close();
      }
      catch (...) 
      {
          // Why did close fail? make sure it *really* does close here
      }
    } 

};

こちらのFAQもご覧ください


この答えを復活させると、最初の例のint foo()ように、関数try-blockを使用して、関数foo全体をtry-catchブロックにラップできます。まだ推奨されるアプローチではありませんが、それは重要なことです。
tyree731

13

C ++のISOドラフトから(ISO / IEC JTC 1 / SC 22 N 4411)

そのため、デストラクタは通常、例外をキャッチし、デストラクタから伝播しないようにする必要があります。

3 tryブロックからthrow式へのパス上に構築された自動オブジェクトのデストラクタを呼び出すプロセスは、「スタック巻き戻し」と呼ばれます。[注:スタックの巻き戻し中に呼び出されたデストラクタが例外を出て終了すると、std :: terminateが呼び出されます(15.5.1)。そのため、デストラクタは通常、例外をキャッチし、デストラクタから伝播しないようにする必要があります。—エンドノート]


1
質問に答えませんでした-OPはすでにこれを認識しています。
アラファンギオン2009年

2
@Arafangion受け入れられた回答がまったく同じことを言ったので、彼がこれに気付いていたのではないか(std :: terminateが呼び出されている)。
lothar 2009年

@Arafangionは、ここでのいくつかの回答と同様、abort()が呼び出されると述べた人もいます。それともstd :: terminateがabort()関数を呼び出しているのでしょうか。
クリシュナオザ2014年

7

デストラクタが他のデストラクタのチェーン内で実行されている可能性があります。即時の呼び出し元によってキャッチされない例外をスローすると、複数のオブジェクトが不整合な状態のままになり、クリーンアップ操作のエラーを無視してさらに多くの問題を引き起こす可能性があります。


7

私は、デストラクタでスローされる「スコープ付きガード」パターンが多くの状況で、特にユニットテストで役立つと考えるグループにいます。ただし、C ++ 11ではstd::terminate、デストラクタに暗黙的に注釈が付けられるため、デストラクタをスローするとが呼び出されることに注意してくださいnoexcept

AndrzejKrzemieńskiは、スローするデストラクタのトピックに関する素晴らしい投稿を持っています。

彼は、C ++ 11にはnoexceptデストラクタのデフォルトをオーバーライドするメカニズムがあると指摘しています。

C ++ 11では、デストラクターは暗黙的にとして指定されnoexceptます。仕様を追加せず、次のようにデストラクタを定義した場合でも、

  class MyType {
        public: ~MyType() { throw Exception(); }            // ...
  };

コンパイラは引き続きnoexcept、デストラクタに仕様を追加します。そして、これは、std::terminate二重例外の状況がなかったとしても、デストラクタが例外をスローした瞬間に呼び出されることを意味します。デストラクタがスローできるようにすることが本当に決まっている場合は、これを明示的に指定する必要があります。次の3つのオプションがあります。

  • デストラクタを明示的にnoexcept(false)
  • デストラクタをとして指定している別のクラスからクラスを継承しますnoexcept(false)
  • デストラクタをとして指定している非静的データメンバーをクラスに配置しますnoexcept(false)

最後に、デストラクタをスローする場合は、二重例外(例外のためにスタックが解放されている間にスローされる)のリスクに常に注意する必要があります。これはへの呼び出しを引き起こし、std::terminateそれがあなたが望むものであることはまれです。この動作を回避するには、を使用して新しい例外をスローする前に、すでに例外があるかどうかを確認するだけstd::uncaught_exception()です。


6

投げるデストラクタがひどい理由を他の誰もが説明しました...あなたはそれについて何ができますか?失敗する可能性のある操作を実行している場合は、クリーンアップを実行し、任意の例外をスローできる別のパブリックメソッドを作成します。ほとんどの場合、ユーザーはそれを無視します。ユーザーがクリーンアップの成功/失敗を監視する場合は、明示的なクリーンアップルーチンを呼び出すだけです。

例えば:

class TempFile {
public:
    TempFile(); // throws if the file couldn't be created
    ~TempFile() throw(); // does nothing if close() was already called; never throws
    void close(); // throws if the file couldn't be deleted (e.g. file is open by another process)
    // the rest of the class omitted...
};

私は解決策を探していますが、彼らは何がなぜ起こったのかを説明しようとしています。デストラクタ内でclose関数が呼び出されることを明確にしたいだけですか?
Jason Liu、

5

良い、包括的かつ正確である主な回答への追加として、私はあなたが参照する記事についてコメントしたいと思います-「デストラクタで例外をスローすることはそれほど悪くない」と言うもの。

この記事は、「例外をスローする代わりの方法は何か」という行を取り、それぞれの方法のいくつかの問題をリストしています。そうすることで、問題のない代替を見つけることができないため、例外をスローし続ける必要があると結論付けます。

問題は、選択肢とともにリストする問題のどれも、「プログラムの未定義の動作」である例外の動作と同じくらい悪いところはないということです。著者の異論には、「美的に醜い」や「悪いスタイルを奨励する」などがあります。どちらがいいですか?スタイルの悪いプログラム、または未定義の動作を示すプログラム?


1
未定義の動作ではなく、すぐに終了します。
マルクファンレーウウェン

規格では「未定義の動作」と記載されています。この動作は頻繁に終了しますが、常に終了するとは限りません。
DJクレイワース2016

いいえ、例外処理->特殊関数の[except.terminate]を読んでください(これは私の標準のコピーでは15.5.1ですが、その番号付けはおそらく古くなっています)。
マルクファンレーウェン

2

Q:それで私の質問はこれです-デストラクタからスローすると未定義の動作が発生する場合、デストラクタ中に発生するエラーをどのように処理しますか?

A:いくつかのオプションがあります。

  1. 他の場所で何が起こっているかに関係なく、例外をデストラクタから流出させます。その際、std :: terminateが後に続く可能性があることに注意してください(または恐れさえしている)。

  2. 例外をデストラクタから流さないでください。ログに書き込むことができますが、可能であれば、いくつかの大きな赤い悪いテキストです。

  3. 私のお気に入りstd::uncaught_exceptionfalseが返された場合、例外をフローさせます。trueが返された場合は、ロギングアプローチにフォールバックします。

しかし、d'torsを投入するのは良いことですか?

上記のほとんどに同意しますが、デストラクタではスローを回避するのが最善です。しかし、それが起こる可能性があることを受け入れ、それをうまく処理することが最善の場合もあります。上記の3つを選択します。

デストラクタからスローするのが実際に素晴らしいアイデアであるいくつかの奇妙なケースがあります。「チェックする必要があります」エラーコードのように。これは、関数から返される値タイプです。呼び出し元が含まれているエラーコードを読み取る/チェックする場合、戻り値はサイレントに破棄します。 ただし、戻り値がスコープ外になるまでに、返されたエラーコードが読み取られていない場合は、デストラクタから例外がスローされます


4
あなたのお気に入りは私が最近試したものであり、それを行うべきではないことがわかりました。gotw.ca/gotw/047.htm
GManNickG

1

私は現在、多くの人が言っているように、クラスがデストラクタから積極的に例外をスローするのではなく、失敗する可能性のある操作を実行するパブリック「クローズ」メソッドを提供するべきであるというポリシーに従っています...

...しかし、コンテナ型のクラスのデストラクタは、ベクトルのように、それらが含むクラスからスローされた例外をマスクすべきではないと私は信じています。この場合、実際には、自分自身を再帰的に呼び出す "free / close"メソッドを使用しています。はい、私は再帰的に言った。この狂気への方法があります。例外の伝播はスタックの存在に依存します。単一の例外が発生した場合でも、残りのデストラクタは両方とも実行され、ルーチンが戻ると保留中の例外が伝播します。これは素晴らしいことです。複数の例外が発生した場合、(コンパイラーに応じて)最初の例外が伝播するか、プログラムが終了します。これは問題ありません。非常に多くの例外が発生し、再帰がスタックをオーバーフローする場合、何かが深刻な問題であり、誰かがそれを発見することになりますが、これも問題ありません。個人的には

要点は、コンテナは中立のままであり、デストラクタからの例外のスローに関して、コンテナが動作するか動作しないかを決定するのは、含まれているクラス次第です。


1

コンストラクターとは異なり、例外のスローはオブジェクトの作成が成功したことを示すのに役立つ場合があるため、デストラクターでは例外をスローしないでください。

この問題は、スタックの巻き戻しプロセス中にデストラクタから例外がスローされたときに発生します。その場合、コンパイラーは、スタックの巻き戻しプロセスを続行するか、新しい例外を処理するかがわからない状況になります。その結果、プログラムはすぐに終了します。

したがって、最善の策は、デストラクタで例外を使用しないことです。代わりに、ログファイルにメッセージを書き込みます。


1
ログファイルにメッセージを書き込むと、例外が発生する可能性があります。
コナード

1

Martin Ba(上記)は正しい方向に向かっています-RELEASEとCOMMITロジックではアーキテクトが異なります。

リリース:

エラーがあれば食べてください。あなたはメモリを解放したり、接続を閉じたりしています。システム内の他の誰もそれらを再び見るべきではなく、OSにリソースを返しています。ここで実際のエラー処理が必要と思われる場合は、オブジェクトモデルの設計上の欠陥が原因である可能性があります。

コミットの場合:

これは、std :: lock_guardなどがmutexに提供しているものと同じ種類のRAIIラッパーオブジェクトが必要な場所です。これらでは、コミットロジックをdtor AT ALLに配置しません。あなたはそれのための専用のAPIを持っていて、それからRAIIがそれらをそれらのdtorsでコミットしてそこでエラーを処理するラッパーオブジェクトがあります。デストラクタで例外をうまく捕捉できることを覚えておいてください。致命的なそれらを発行します。これにより、別のラッパー(例:std :: unique_lockとstd :: lock_guard)を構築するだけで、ポリシーとさまざまなエラー処理を実装することができ、唯一の方法であるコミットロジックを呼び出すことを忘れないでください。そもそもそれをdtorに入れる正当な理由。


1

だから私の質問はこれです-デストラクタからスローすると未定義の動作が発生する場合、デストラクタ中に発生したエラーをどのように処理しますか?

主な問題はこれです:失敗することはできませ。結局、失敗しないとはどういう意味ですか?データベースへのトランザクションのコミットが失敗し、失敗する(ロールバックに失敗する)場合、データの整合性はどうなりますか?

デストラクタは通常のパスと例外的な(失敗した)パスの両方で呼び出されるため、それら自体が失敗することはありません。

これは概念的に難しい問題ですが、多くの場合、解決策は、失敗が失敗しないことを確認する方法を見つけることです。たとえば、データベースは、外部データ構造またはファイルにコミットする前に変更を書き込む場合があります。トランザクションが失敗した場合、ファイル/データ構造は破棄されます。次に、外部構造/ファイルからの変更をコミットしても、失敗しないアトミックトランザクションであることを確認する必要があります。

実用的な解決策はおそらく、失敗したときに失敗する可能性が天文学的にありそうもないことを確認することです。

私にとって最も適切な解決策は、クリーンアップロジックが失敗しないように非クリーンアップロジックを記述することです。たとえば、既存のデータ構造をクリーンアップするために新しいデータ構造を作成したい場合は、デストラクタ内に作成する必要がないように、その補助構造を事前に作成することをお勧めします。

確かに、これは言うよりもはるかに簡単に言えますが、それは私がそれについて取り組むために私が見る唯一の本当に適切な方法です。時には、デストラクタが両方を処理しようとすることで2倍の責任を負っているように感じる場合があるため、例外的なものから離れた通常の実行パスに対して個別のデストラクタロジックを作成する機能があるはずだと思います(例として、明示的な破棄を必要とするスコープガードがあります) ;例外的な破壊パスと例外的でない破壊パスを区別できれば、これは必要ありません)。

それでも最終的な問題は失敗しないわけにはいかないことであり、それはすべてのケースで完全に解決するのは難しい概念設計の問題です。多数の小さなオブジェクトが相互に作用しあう複雑な制御構造に包まれすぎないようにして、代わりに設計をややかさばる方法でモデル化します(例:粒子全体を破壊するデストラクタを備えた粒子システム)システム、粒子ごとの独立した重要なデストラクタではありません)。このような大まかなレベルで設計をモデル化すると、対処する必要のあるデストラクタが少なくなり、デストラクタが失敗しないことを確認するために必要なメモリ/処理のオーバーヘッドも許容できることがよくあります。

そして、それはデストラクタの使用頻度を減らすことが最も簡単な解決策の1つです。上記のパーティクルの例では、パーティクルを破壊/削除する際に、何らかの理由で失敗する可能性のあるいくつかの処理を行う必要があります。その場合、例外的なパスで実行される可能性のあるパーティクルのdtorを介してこのようなロジックを呼び出す代わりに、パーティクルを削除するときに、パーティクルシステムによってすべてを実行させることができます。パーティクルの削除は、例外のないパスの間に常に行われる場合があります。システムが破壊された場合、おそらくすべてのパーティクルをパージし、失敗する可能性のある個々のパーティクル削除ロジックに煩わされることはありませんが、失敗する可能性のあるロジックは、パーティクルシステムの通常の実行中にのみ実行され、1つ以上のパーティクルを削除します。

重要なデストラクタを使用して多数の小さなオブジェクトを処理することを避けた場合に発生するような解決策がよくあります。例外安全であることがほとんど不可能であると思われる混乱に巻き込まれる可能性があるのは、すべてが重要なdtorを持つ多くの小さなオブジェクトに巻き込まれた場合です。

nothrow / noexceptを実際にコンパイラエラーに変換すると、それを指定する何か(その基本クラスのnoexcept仕様を継承する必要がある仮想関数を含む)がスローできるものを呼び出そうとした場合に役立ちます。この方法では、実際にスローする可能性のあるデストラクタを誤って作成した場合、コンパイル時にこれらすべてのものをキャッチできます。


1
破壊は今失敗ですか?
curiousguy

彼は、失敗時にデストラクタが呼び出され、その失敗をクリーンアップすることを意味していると思います。したがって、アクティブな例外中にデストラクタが呼び出された場合、デストラクタは前回の失敗からのクリーンアップに失敗しています。
user2445507 2019年

0

アラームイベントを設定します。通常、アラームイベントは、オブジェクトのクリーンアップ中に障害を通知する優れた形式です

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.