12

したがって、C++ を使用して例外をネストする方法std::nested_exceptionは次のとおりです。

void foo() {
  try {
    // code that might throw
    std::ifstream file("nonexistent.file");
    file.exceptions(std::ios_base::failbit);
  }

  catch(...) {
    std::throw_with_nested(std::runtime_error("foo failed"));
  }
}

しかし、この手法では、例外をネストしたいすべてのレベルで明示的な try/catch ブロックを使用します。これは控えめに言っても見苦しいものです。

Jon Kalbが「責任の取得は初期化である」と拡張した RAII は、明示的な try/catch ブロックを使用する代わりに、例外を処理するためのはるかにクリーンな方法です。RAII では、明示的な try/catch ブロックは、ユーザーにエラー メッセージを表示するなど、最終的に例外を処理するためにのみ主に使用されます。

上記のコードを見ると、入るfoo()ことは、例外を報告しstd::runtime_error("foo failed")、nested_exception 内に詳細をネストする責任を伴うと見なすことができるように思えます。RAII を使用してこの責任を負うことができれば、コードはよりきれいに見えます。

void foo() {
  Throw_with_nested on_error("foo failed");

  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}

ここで RAII 構文を使用して、明示的な try/catch ブロックを置き換える方法はありますか?


これを行うには、デストラクタが呼び出されたときに、デストラクタの呼び出しが例外によるものかどうかを確認し、そうであればその例外をネストし、巻き戻しが正常に続行されるように、ネストされた新しい例外をスローする型が必要です。それは次のようになります。

struct Throw_with_nested {
  const char *msg;

  Throw_with_nested(const char *error_message) : msg(error_message) {}

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      std::throw_with_nested(std::runtime_error(msg));
    }
  }
};

ただしstd::throw_with_nested()、「現在処理されている例外」がアクティブである必要があります。つまり、catch ブロックのコンテキスト内以外では機能しません。したがって、次のようなものが必要です。

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      try {
        rethrow_uncaught_exception();
      }
      catch(...) {
        std::throw_with_nested(std::runtime_error(msg));
      }
    }
  }

残念ながら、私の知る限りrethrow_uncaught_excpetion()、C++ で定義されているようなものはありません。

4

2 に答える 2

3

RAIIでは無理

簡単なルールを考えると

デストラクタは決してスローしてはなりません。

RAII で必要なものを実装することは不可能です。このルールには単純な理由が 1 つあります。飛行中の例外が原因でスタックの巻き戻し中にデストラクタが例外をスローした場合、terminate()が呼び出され、アプリケーションが停止します。

代替案

C++11 では、作業を少し楽にするラムダを使用できます。あなたは書ける

void foo()
{
    giveErrorContextOnFailure( "foo failed", [&]
    {
        // code that might throw
        std::ifstream file("nonexistent.file");
        file.exceptions(std::ios_base::failbit);
    } );
}

次の方法で関数を実装する場合giveErrorContextOnFailure:

template <typename F>
auto giveErrorContextOnFailure( const char * msg, F && f ) -> decltype(f())
{
    try { return f(); }
    catch { std::throw_with_nested(std::runtime_error(msg)); }
}

これにはいくつかの利点があります。

  • エラーがどのようにネストされているかをカプセル化します。
  • エラーがネストされる方法を変更することは、このテクニックがプログラム全体で厳密に守られている場合、プログラム全体で変更できます。
  • エラーメッセージは、RAII と同様にコードの前に記述できます。この手法は、ネストされたスコープにも使用できます。
  • tryコードの繰り返しが少なくなります: 、catchstd::throw_with_nestedおよびを記述する必要はありませんstd::runtime_error。これにより、コードの保守が容易になります。プログラムの動作を変更したい場合は、コードを 1 か所だけ変更する必要があります。
  • 戻り値の型は自動的に推測されます。したがって、関数foo()が何かを返す必要がある場合は、関数 foo() のreturn前に追加するだけです。giveErrorContextOnFailure

テンプレートはデフォルトでインライン化されているため、リリース モードでは通常、try-catch の方法と比較してパフォーマンスに関する問題はありません。

従うべきもう 1 つの興味深いルール:

使用しないでくださいstd::uncaught_exception()

このトピックについて Herb Sutter による素晴らしい記事があり、このルールを完全に説明しています。要するに、次のように、スタックの巻き戻し中にデストラクタ内からf()呼び出される関数がある場合

void f()
{
    RAII r;
    bla();
}

のデストラクタはRAII次のようになります

RAII::~RAII()
{
    if ( std::uncaught_exception() )
    {
        // ...
    }
    else
    {
        // ...
    }
}

std::uncaught_exception()のデストラクタを含むそのデストラクタから呼び出された関数内であっても、スタックの巻き戻し中の外側のデストラクタでは常に true を返すため、デストラクタの最初の分岐が常に実行されますRAII

于 2013-11-18T10:31:57.650 に答える
3

デストラクタでキャッチされていない例外をキャッチ (および消費) するメソッドがない場合、ネストされているかどうかにかかわらず、デストラクタのコンテキストでstd::terminate呼び出されずに例外を再スローする方法はありません (例外がコンテキストでスローされる場合)。例外処理の)。

std::current_exception(と組み合わせるstd::rethrow_exception) は、現在処理されている例外へのポインターのみを返します。この場合の例外は明示的に処理されないため、このシナリオでは使用できません。

上記を考えると、唯一の答えは審美的な観点からのものです。関数レベルの try ブロックにより、見栄えが少し悪くなります。(スタイルの好みに合わせて調整してください):

void foo() try {
  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}
catch(...) {
  std::throw_with_nested(std::runtime_error("foo failed"));
}
于 2013-11-17T23:45:56.783 に答える