C ++は 'finally'ブロックをサポートしていますか?(そして、私が耳にしているこの「RAII」とは何ですか?)


回答:


273

いいえ、C ++は 'finally'ブロックをサポートしていません。その理由は、C ++が代わりにRAIIをサポートしているためです。「リソースの取得は初期化です」- 本当に有用な概念の悪い名前

アイデアは、オブジェクトのデストラクタがリソースの解放を担当するというものです。オブジェクトに自動ストレージ期間がある場合、オブジェクトのデストラクタは、それが作成されたブロックが存在するときに呼び出されます。例外が存在する場合でも、そのブロックが存在する場合でも同様です。これは、Bjarne Stroustrupによるトピックの説明です。

RAIIの一般的な用途は、ミューテックスのロックです。

// A class with implements RAII
class lock
{
    mutex &m_;

public:
    lock(mutex &m)
      : m_(m)
    {
        m.acquire();
    }
    ~lock()
    {
        m_.release();
    }
};

// A class which uses 'mutex' and 'lock' objects
class foo
{
    mutex mutex_; // mutex for locking 'foo' object
public:
    void bar()
    {
        lock scopeLock(mutex_); // lock object.

        foobar(); // an operation which may throw an exception

        // scopeLock will be destructed even if an exception
        // occurs, which will release the mutex and allow
        // other functions to lock the object and run.
    }
};

RAIIは、オブジェクトを他のクラスのメンバーとして使用することも簡単にします。所有するクラスが破棄されると、RAII管理クラスのデストラクタが結果として呼び出されるため、RAIIクラスによって管理されるリソースが解放されます。つまり、リソースを管理するクラスのすべてのメンバーにRAIIを使用すると、メンバーのリソースの有効期間を手動で管理する必要がないため、オーナークラスの非常にシンプルな、おそらくデフォルトのデストラクタを使用して問題を回避できます。 。(これを指摘してくれたMike Bに感謝します。)

C#またはVB.NETを使い慣れている方は、RAIIがIDisposableおよび 'using'ステートメントを使用した.NETの確定的破壊に似ていることに気付くかもしれません。実際、2つの方法は非常によく似ています。主な違いは、RAIIがメモリを含むすべてのタイプのリソースを確定的に解放することです。.NETでIDisposableを実装すると(.NET言語のC ++ / CLIでも)、メモリを除いてリソースが確定的に解放されます。.NETでは、メモリは確定的に解放されません。メモリはガベージコレクションサイクル中にのみ解放されます。

 

†一部の人々は、「破壊は資源放棄である」がRAIIイディオムのより正確な名前であると信じています。


18
「破壊は資源放棄」-DIRR ...いいえ、私にはうまくいきません。= P
エリックフォーブス

14
RAIIは行き詰まっています。実際、変更はありません。そうしようとするのはばかげているでしょう。ただし、「リソースの取得は初期化」はまだかなり貧弱な名前であることを認めなければなりません。
ケビン、

162
SBRM ==スコープバウンドリソース管理
Johannes Schaub-litb '24年

10
ソフトウェアだけでなく、一般に改良された技術を設計するスキルを持つ人は、そのような恐ろしい頭字語に値する言い訳をすることができません。
Hardryv 2012

54
これにより、C ++オブジェクトの存続期間と一致しないクリーンアップ対象がある場合は、スタックしたままになります。最終的には、Lifetime Equals C ++ Class LiftimeまたはEls It Gets Ugly(LECCLEOEIGU?)になると思います。
ウォーレンP

79

C ++では、RAIIのため、finallyは必要ありません

RAIIは、例外安全の責任をオブジェクトのユーザーからオブジェクトの設計者(および実装者)に移します。(設計/実装で)例外の安全性を1度正しく修正するだけでよいので、これは正しい場所だと主張します。最終的に使用することにより、オブジェクトを使用するたびに例外の安全性を正しく取得する必要があります。

また、IMOのコードはきれいに見えます(以下を参照)。

例:

データベースオブジェクト。DB接続が使用されていることを確認するには、DB接続を開いて閉じる必要があります。RAIIを使用することにより、これはコンストラクター/デストラクターで行うことができます。

RAIIのようなC ++

void someFunc()
{
    DB    db("DBDesciptionString");
    // Use the db object.

} // db goes out of scope and destructor closes the connection.
  // This happens even in the presence of exceptions.

RAIIを使用すると、DBオブジェクトを正しく簡単に使用できます。DBオブジェクトは、デストラクタを使用してどのように使用したとしても、それ自体を正しく閉じます。

最後にJavaのような

void someFunc()
{
    DB      db = new DB("DBDesciptionString");
    try
    {
        // Use the db object.
    }
    finally
    {
        // Can not rely on finaliser.
        // So we must explicitly close the connection.
        try
        {
            db.close();
        }
        catch(Throwable e)
        {
           /* Ignore */
           // Make sure not to throw exception if one is already propagating.
        }
    }
}

最終的に使用する場合、オブジェクトの正しい使用はオブジェクトのユーザーに委任されます。すなわち、DB接続を明示的に閉じるのは、オブジェクトユーザーの責任です。これはファイナライザーで実行できると主張できますが、リソースの可用性やその他の制約が制限されている可能性があるため、通常はオブジェクトのリリースを制御し、ガベージコレクターの非決定的な動作に依存しないようにします。

これも簡単な例です。
複数のリソースを解放する必要がある場合、コードは複雑になる可能性があります。

より詳細な分析はここで見つけることができます:http : //accu.org/index.php/journals/236


16
// Make sure not to throw exception if one is already propagating.このため、C ++デストラクタでも例外をスローしないことが重要です。
Cemafor 2013年

10
@Cemafor:C ++がデストラクタから例外をスローしない理由は、Javaとは異なります。Javaでは機能します(元の例外を失うだけです)。C ++では本当に悪い。しかし、C ++の要点は、クラスの設計者がデストラクタを作成するときに一度だけ(クラスの設計者が)実行する必要があることです。Javaでは、使用時にそれを実行する必要があります。そのため、同じボイラープレートをすぐに作成するのは、クラスのユーザーの責任です。
マーティンヨーク

1
「必要」ということであれば、RAIIも必要ありません。それを取り除こう!:-)冗談はさておき、RAIIは多くの場合問題ありません。上記のコードが早期に戻ったとしても、RAIIが行うのがより面倒なのは、(リソースに関連しない)コードを実行したい場合です。そのためには、gotosを使用するか、それを2つの方法に分けます。
トリニダード

1
@トリニダード:それはあなたが考えるほど単純ではありません(あなたの提案はすべて、可能な限り最悪のオプションを選ぶようです)。これが、コメントよりも質問を検討するのに適している理由です。
マーティンヨーク

1
「RAIIのために必要ではない」と批判する:アドホックRAIIを追加することは、ボイラープレートコードを追加するには多すぎる場合が多く、try-finallyは非常に適切です。
ceztko

63

通常、RAIIの方が優れていますが、C ++でfinallyセマンティクスを簡単に使用できます。少量のコードを使用する。

その上、C ++ コアガイドラインが最終的に与えます。

これは、GSL Microsoft実装へのリンクとMartin Moene実装へのリンクです。

Bjarne Stroustrupは、GSLにあるすべてのものは最終的には標準に入るつもりであると何度も言っています。したがって、finallyを将来的に使用できるようにする必要があります。

ただし、必要に応じて簡単に実装できます。

C ++ 11では、RAIIとラムダにより、最終的に将軍を作成できます。

namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
    FinalAction(F f) : clean_{f} {}
   ~FinalAction() { if(enabled_) clean_(); }
    void disable() { enabled_ = false; };
  private:
    F clean_;
    bool enabled_{true}; }; }

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

使用例:

#include <iostream>
int main() {
    int* a = new int;
    auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
    std::cout << "doing something ...\n"; }

出力は次のようになります。

doing something...
leaving the block, deleting a!

個人的には、C ++プログラムでPOSIXファイル記述子を確実に閉じるために、これを数回使用しました。

リソースを管理し、あらゆる種類のリークを回避する実際のクラスを用意することは、通常はより良い方法ですが、クラスを作りすぎると聞こえる場合に、これは最終的に役立ちます。

また、私のようなそれより良い他の言語よりも最終的には自然に使用している場合ので、あなたは(私の例では、開口部コード近くの決算コードを書く新しいおよび削除)C ++でいつものようにLIFO順で破壊下記の建設を。唯一の欠点は、実際には使用しない自動変数を取得し、ラムダ構文によって少しうるさくなることです(私の例では、4行目の例では、最終的に単語というだけで、右側の{}ブロックは意味があります。残りは本質的にノイズです)。

もう一つの例:

 [...]
 auto precision = std::cout.precision();
 auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
 std::cout << std::setprecision(3);

無効ならばメンバーは便利です最終的には唯一の障害が発生した場合に呼び出されることがあります。たとえば、3つの異なるコンテナにオブジェクトをコピーする必要がある場合、finallyを設定して、各コピーを元に戻し、すべてのコピーが成功した後に無効にすることができます。そうすることで、破壊がスローできない場合、強力な保証が保証されます。

例を無効にする

//strong guarantee
void copy_to_all(BIGobj const& a) {
    first_.push_back(a);
    auto undo_first_push = finally([first_&] { first_.pop_back(); });

    second_.push_back(a);
    auto undo_second_push = finally([second_&] { second_.pop_back(); });

    third_.push_back(a);
    //no necessary, put just to make easier to add containers in the future
    auto undo_third_push = finally([third_&] { third_.pop_back(); });

    undo_first_push.disable();
    undo_second_push.disable();
    undo_third_push.disable(); }

C ++ 11を使用できない場合でも、finallyを使用できますが、コードは少し長くなります。コンストラクタとデストラクタのみで構造体を定義するだけで、コンストラクタは必要なものへの参照を取得し、デストラクタが必要なアクションを実行します。これは基本的に手動で行われるラムダの動作です。

#include <iostream>
int main() {
    int* a = new int;

    struct Delete_a_t {
        Delete_a_t(int* p) : p_(p) {}
       ~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
        int* p_;
    } delete_a(a);

    std::cout << "doing something ...\n"; }

可能性のある問題がある可能性があります:関数 'finally(F f)'ではFinalActionのオブジェクトを返すため、最終的に関数が返される前にデコンストラクターが呼び出される可能性があります。多分私達ではなく、テンプレートF.のはstd ::機能を使用する必要があります
user1633272

注意FinalAction基本的に人気が同じであるScopeGuardだけ別の名前で、イディオム。
アンデラ

1
この最適化は安全ですか?
Nulano 2017

2
@ Paolo.Bolzoni返信が遅くなってすみません、コメントの通知が届きませんでした。(変数が使用されていないため)最終ブロック(DLL関数を呼び出す)がスコープの終了前に呼び出されるのではないかと心配していました。リンクしますが、残念ながらもう見つかりません。
ヌラーノ2017

1
disable()関数は、他の点ではクリーンな設計の一種のイボです。失敗した場合にのみ最終的に呼び出されるようにするには、catchステートメントを使用しないのはなぜですか?それは何のためか?
user2445507 2019年

32

RAIIは、スタックベースのオブジェクトでクリーンアップを簡単にするだけでなく、オブジェクトが別のクラスのメンバーである場合にも同じ「自動」クリーンアップが発生するため便利です。所有クラスが破棄されると、結果としてそのクラスのdtorが呼び出されるため、RAIIクラスによって管理されるリソースがクリーンアップされます。

つまり、RAIIニルバーナに到達し、クラスのすべてのメンバーがRAII(スマートポインターのような)を使用する場合、所有者クラスは手動で管理する必要がないため、非常にシンプルな(おそらくデフォルトの)dtorで済むことができます。メンバーリソースのライフタイム。


それは非常に良い点です。あなたに+1します。他の多くの人があなたに賛成票を投じていません。私の投稿があなたのコメントを含むように編集されても構わないと思います。(もちろんクレジットを差し上げました。)ありがとうございます。:)
ケビン、

30

とにかくガベージコレクターによってリソースが自動的に割り当て解除されているにもかかわらず、マネージ言語でさえ最終ブロックを提供するのはなぜですか?

実際、ガベージコレクターに基づく言語には、「最終的に」さらに多くのものが必要です。ガベージコレクターはオブジェクトを適時に破棄しません。そのため、メモリ以外の問題を正しくクリーンアップすることはできません。

動的に割り当てられたデータに関しては、スマートポインターを使用する必要があると多くの人が主張します。

しかしながら...

RAIIは例外の安全性の責任をオブジェクトのユーザーからデザイナーに移します

悲しいことに、これはそれ自体の没落です。古いCプログラミングの習慣はひどく死にます。Cまたは非常にCスタイルで記述されたライブラリを使用している場合、RAIIは使用されません。APIフロントエンド全体を書き直すのではなく、それはまさにあなたが作業しなければならないことです。 次に、「最終的に」の欠如は本当に噛み付きます。


13
まさに... RAIIは理想的な見地からはいいようです。しかし、従来のC API(Win32 APIのCスタイル関数など)を常に使用する必要があります。ある種のHANDLEを返すリソースを取得することは非常に一般的であり、クリーンアップするにはCloseHandle(HANDLE)などの関数が必要です。try ...を使用することは、可能性のある例外に対処する良い方法です。(ありがたいことに、カスタムの削除機能を備えたshared_ptrとC ++ 11のラムダは、クラス全体を記述して1つの場所でのみ使用するAPIをラップする必要がない、RAIIベースの救済を提供するようです。)
James Johnston

7
@JamesJohnstonは、あらゆる種類のハンドルを保持し、RAII機構を提供するラッパークラスを作成するのが非常に簡単です。たとえば、ATLはそれらの束を提供します。これは面倒すぎると思っているようですが、非常に小さく、書きやすいとは思いません。
Mark Ransom 2012年

5
シンプルはい、小さいいいえ。サイズは、使用しているライブラリの複雑さによって異なります。
Philip Couling、2012年

1
@MarkRansom:別の例外が保留されているときにクリーンアップ中に例外が発生した場合に、RAIIがインテリジェントな何かを実行できるメカニズムはありますか?try / finallyを備えたシステムでは、(厄介ではありますが)保留中の例外とクリーンアップ中に発生した例外の両方が新しいに格納されるように調整することが可能ですCleanupFailedException。RAIIを使用してそのような結果を達成するもっともらしい方法はありますか?
スーパーキャット2012年

3
@couling:プログラムがSomeObject.DoSomething()メソッドを呼び出して、それが(1)成功した​​か、(2)副作用なしで失敗したか、(3)呼び出し側が対処する準備ができた副作用で失敗したかどうかを知りたい場合が多くあります。、または(4)呼び出し元が対処できない副作用により失敗した。呼び出し側だけが、対処できる状況と対処できない状況を認識します。発信者に必要なのは、状況を知る方法です。例外に関する最も重要な情報を提供するための標準的なメカニズムがないのは残念です。
supercat

9

C ++ 11ラムダ関数を使用したもう1つの「最後の」ブロックエミュレーション

template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
    try
    {
        code();
    }
    catch (...)
    {
        try
        {
            finally_code();
        }
        catch (...) // Maybe stupid check that finally_code mustn't throw.
        {
            std::terminate();
        }
        throw;
    }
    finally_code();
}

コンパイラが上記のコードを最適化することを期待しましょう。

これで、次のようなコードを記述できます。

with_finally(
    [&]()
    {
        try
        {
            // Doing some stuff that may throw an exception
        }
        catch (const exception1 &)
        {
            // Handling first class of exceptions
        }
        catch (const exception2 &)
        {
            // Handling another class of exceptions
        }
        // Some classes of exceptions can be still unhandled
    },
    [&]() // finally
    {
        // This code will be executed in all three cases:
        //   1) exception was not thrown at all
        //   2) exception was handled by one of the "catch" blocks above
        //   3) exception was not handled by any of the "catch" block above
    }
);

必要に応じて、このイディオムを「try-finally」マクロにラップできます。

// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};

#define begin_try    with_finally([&](){ try
#define finally      catch(never_thrown_exception){throw;} },[&]()
#define end_try      ) // sorry for "pascalish" style :(

「最終的に」ブロックがC ++ 11で利用可能になりました。

begin_try
{
    // A code that may throw
}
catch (const some_exception &)
{
    // Handling some exceptions
}
finally
{
    // A code that is always executed
}
end_try; // Sorry again for this ugly thing

個人的には、「マクロ」バージョンの「finally」イディオムは好きではありません。その場合、構文がよりかさばりますが、純粋な「with_finally」関数を使用することを好みます。

上記のコードをここでテストできます:http : //coliru.stacked-crooked.com/a/1d88f64cb27b3813

PS

コードで最終的にブロックが必要な場合は、スコープ付きガードまたはON_FINALLY / ON_EXCEPTIONマクロがおそらくニーズによりよく適合します。

ON_FINALLY / ON_EXCEPTIONの使用例を次に示します。

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...

1
最初は、このページに表示されるすべてのオプションの中で最も読みやすいものです。+1
ニコス

7

このような古いスレッドを掘り下げて申し訳ありませんが、次の理由には大きなエラーがあります:

RAIIは、例外安全の責任をオブジェクトのユーザーからオブジェクトの設計者(および実装者)に移します。(設計/実装で)例外の安全性を1度正しく修正するだけでよいので、これは正しい場所だと主張します。最終的に使用することにより、オブジェクトを使用するたびに例外の安全性を正しく取得する必要があります。

多くの場合、動的に割り当てられたオブジェクト、オブジェクトの動的な数などを処理する必要があります。tryブロック内で、一部のコードは多数のオブジェクト(実行時に決定される数)を作成し、それらへのポインターをリストに格納します。現在、これは珍しいシナリオではありませんが、非常に一般的です。この場合、次のようなものを書きたいと思います

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  finally
  {
    while (!myList.empty())
    {
      delete myList.back();
      myList.pop_back();
    }
  }
}

もちろん、リスト自体はスコープ外に出ると破棄されますが、作成した一時オブジェクトはクリーンアップされません。

代わりに、醜いルートをたどる必要があります。

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  catch(...)
  {
  }

  while (!myList.empty())
  {
    delete myList.back();
    myList.pop_back();
  }
}

また、とにかくガベージコレクターによってリソースが自動的に割り当て解除されているにもかかわらず、マネージ言語でさえ最終ブロックを提供するのはなぜですか?

ヒント:メモリの割り当てを解除するだけでなく、「最終的に」実行できることは他にもあります。


17
マネージ言語は、1種類のリソース(メモリ)だけが自動的に管理されるため、最終的にブロックを正確に必要とします。RAIIは、すべてのリソースを同じ方法で処理できるため、最終的には不要であることを意味します。(ネイキッドポインターの代わりにリストでスマートポインターを使用して)サンプルで実際にRAIIを使用した場合、コードは "最終"の例よりも単純になります。また、newの戻り値をチェックしない場合はさらに簡単になります-チェックしてもほとんど意味がありません。
Myto

7
newNULLを返さず、代わりに例外をスローします
Hasturkun

5
あなたは重要な質問を投げかけますが、それは2つの可能な答えを持っています。1つは、Mytoによって与えられたものです。すべての動的割り当てにスマートポインタを使用します。もう1つは、標準のコンテナーを使用することです。これにより、破棄時に常に内容が破棄されます。どちらの方法でも、割り当てられたすべてのオブジェクトは、最終的には、静的に割り当てられたオブジェクトによって所有され、破棄時に自動的に解放されます。プレーンポインターと配列の可視性が高いため、これらの優れたソリューションをプログラマーが発見するのが難しいのは本当に残念です。
j_random_hacker

4
C ++ 11は、これを改善し、含みstd::shared_ptr、およびstd::unique_ptr直接STDLIBです。
u0b34a0f6ae 2011年

16
あなたの例がそれほどひどい見た目である理由は、RAIIに欠陥があるからではなく、むしろそれを使用できなかったためです。生のポインタはRAIIではありません。
Ben Voigt 2013

6

FWIW、Microsoft Visual C ++は、最終的にtryをサポートします。これは、MFCアプリで、クラッシュの原因となる深刻な例外をキャッチする方法として歴史的に使用されてきました。例えば;

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}

私は過去にこれを使って、終了する前に開いているファイルのバックアップを保存するようなことをしました。ただし、特定のJITデバッグ設定はこのメカニズムを壊します。


4
これは実際にはC ++の例外ではなく、SEHの例外であることを覚えておいてください。MS C ++コードでは両方を使用できます。SEHは、VB、.NETが例外を実装する方法であるOS例外ハンドラーです。
gbjbaanb 2008年

また、SetUnhandledExceptionHandlerを使用して、キャッチされていない「グローバルな」例外ハンドラを作成できます-SEH例外用。
gbjbaanb 2008年

3
SEHは恐ろしいものであり、C ++デストラクタが呼び出されないようにもします
paulm

6

他の回答で指摘されているように、C ++はのfinallyような機能をサポートできます。この機能の実装は、おそらく標準言語の一部に最も近いものであり、Bjarne StoustrupとHerb Sutterによって編集されたC ++を使用するための一連のベストプラクティスであるC ++ Core Guidelinesに付随するものです。の実装はfinallyガイドラインサポートライブラリ(GSL)の一部です。ガイドライン全体を通して、finally古いスタイルのインターフェースを扱う場合は、の使用をお勧めします。また、適切なリソースハンドルが利用できない場合は、「final_actionオブジェクトを使用してクリーンアップを表現する」というタイトルの独自のガイドラインもあります。。。

したがって、C ++だけでなく、 finally、実際に多くの一般的なユースケースで使用することをお勧めします。

GSL実装の使用例は次のようになります。

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // Do a lot of stuff, return early and throw exceptions.
    // clean_that_resource will always get called.
}

GSLの実装と使用法は、Paolo.Bolzoniの回答と非常によく似ています。1つの違いは、によって作成されたオブジェクトにgsl::finally()disable()呼び出しがないことです。その機能が必要な場合(たとえば、リソースがアセンブルされ、例外が発生しないことになったらリソースを返すため)、Paoloの実装を好むかもしれません。それ以外の場合、GSLの使用は、標準化された機能を使用するのと同じくらい簡単です。


3

実際にはそうではありませんが、たとえば、次のように、それらをある程度エミュレートできます。

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;

元の例外が再スローされる前に、finallyブロック自体が例外をスローし、それによって元の例外を破棄する可能性があることに注意してください。これは、Javaのfinal-blockとまったく同じ動作です。また、returntry&catchブロック内では使用できません。


3
最終的にブロックがスローされるかもしれないと言ってよかったです。これは、「RAIIを使用する」という回答のほとんどが無視しているように見えるものです。最後に二回ブロック書き込みすることを避けるために、あなたのような何かができるstd::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
sethobrien

1
私が知りたかったのはそれだけです!他のどの回答もcatch(...)+空のスローを説明しなかったのはなぜですか。最終的にブロックのように動作しますか?時にはそれだけが必要です。
VinGarcia 2016

私の回答で提供したソリューション(stackoverflow.com/a/38701485/566849)は、finallyブロック内から例外をスローできるようにする必要があります。
Fabio A.

3

ほぼ Java のキーワードのようfinallyに使用できるマクロを思いつきました。これは、ラムダ関数などのフレンドを使用するため、それ以上が必要です。また、clangでもサポートされている複合ステートメント式の GCC拡張を利用します。finallystd::exception_ptrstd::promiseC++11

警告:この回答の以前のバージョンでは、コンセプトの異なる実装を使用しましたが、さらに多くの制限がありました。

まず、ヘルパークラスを定義しましょう。

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(body);
}

次に、実際のマクロがあります。

#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try 
#define finally });                         \
        true;                               \
        ({return __finally_helper.get();})) \
/***/

次のように使用できます。

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

を使用std::promiseすると、実装が非常に簡単になりますが、おそらく、不要なオーバーヘッドもかなり導入されるため、から必要な機能のみを再実装することで回避できますstd::promise


¹ 警告:のJavaバージョンのように動作しないことがいくつかありfinallyます。私の頭の上から:

  1. and break内からのステートメントで外側のループから抜けることはできませんtrycatch()彼らはラムダ関数内で生きているので、のブロック。
  2. catch()後に少なくとも1つのブロックが必要ですtry必要です。これはC ++の要件です。
  3. 関数がvoid以外の戻り値を持っているが、tryand catch()'sブロック内に戻り値がない場合、finallyマクロはを返すコードに展開されるため、コンパイルは失敗しますvoid。これは間違いかもしれませんfinally_noreturn一種のマクロを持つことによって無効にます

全体として、私がこれを自分で使用するかどうかはわかりませんが、それを使って遊ぶのは楽しかったです。:)


ええ、それはただの高速なハックでしたが、プログラマーが彼らがやっていることを知っているなら、それにもかかわらずそれは有用かもしれません。
Fabio A.

@MarkLakata、例外をスローして返すことをサポートするより良い実装で投稿を更新しました。
Fabio A.

いいね。マクロcatch(xxx) {}の先頭に不可能なブロックを置くだけで警告2を取り除くことができますfinally。xxxは、少なくとも1つのcatchブロックを持たせるためだけの偽のタイプです。
Mark Lakata

@MarkLakata、私もそのことを考えましたが、それではを使用できcatch(...)なくなりますね。
Fabio A.

そうは思いません。xxx決して使用されないプライベート名前空間で、あいまいなタイプを構成するだけです。
Mark Lakata

2

私がすべきだと思うユースケースがありますfinally 、私はビューの流れの観点から読みやすいと思いとして、C ++ 11言語の完全に受け入れ一部では。私のユースケースは、消費者/生産者のスレッドチェーンです。nullptrすべてのスレッドをシャットダウンするために実行の最後にが送信されます。

C ++でサポートされている場合は、コードを次のようにする必要があります。

    extern Queue downstream, upstream;

    int Example()
    {
        try
        {
           while(!ExitRequested())
           {
             X* x = upstream.pop();
             if (!x) break;
             x->doSomething();
             downstream.push(x);
           } 
        }
        finally { 
            downstream.push(nullptr);
        }
    }

ループが終了した後に発生するため、これは最終的に宣言をループの先頭に置くよりも論理的だと思いますが、C ++では実行できないため、これは希望的な考えです。キューdownstreamは別のスレッドに接続されているので、この時点では破棄できないためpush(nullptr)、デストラクタにセンチネルを入れるdownstreamことはできません。他のスレッドが受信するまで生き続ける必要があります。nullptr。ます。

したがって、これはラムダでRAIIクラスを使用して同じことを行う方法です。

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

そしてこれがあなたの使い方です:

    extern Queue downstream, upstream;

    int Example()
    {
        Finally atEnd([](){ 
           downstream.push(nullptr);
        });
        while(!ExitRequested())
        {
           X* x = upstream.pop();
           if (!x) break;
           x->doSomething();
           downstream.push(x);
        }
    }

こんにちは、私は上記の回答(stackoverflow.com/a/38701485/566849)がお客様の要件を完全に満たしていると思います。
Fabio A.

1

多くの人が述べたように、解決策は、C ++ 11機能を使用して、finallyブロックを回避することです。機能の1つはunique_ptrです。

これは、RAIIパターンを使用して書かれたMephaneの回答です。

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

C ++標準ライブラリコンテナでのunique_ptrの使用に関するいくつかの紹介はこちらです


0

代替案を提供したいと思います。

最終的にブロックが常に呼び出されるようにしたい場合は、最後のcatchブロックの後に置くだけです(これはおそらくcatch( ... )不明な例外をキャッチするためのものです)

try{
   // something that might throw exception
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp(); 

例外がスローされたときに最後にブロックする必要がある場合は、ブールローカル変数を使用できます。実行する前にfalseに設定し、tryブロックの最後にtrue割り当てを配置してから、catchブロックの後に変数をチェックします。値:

bool generalAppState = false;
try{
   // something that might throw exception

   //the very end of try block:
   generalAppState = true;
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
   doSomeCleanUpOfDirtyEnd();
}

//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
   cleanEnd();
}

これは機能しません。finally ブロックの目的は、コードで例外がコードブロックを離れることを許可する必要がある場合でもクリーンアップを実行することです。考えてみてください: `試行{//「B」をスローする可能性があるもの} catch(A&a){}最後に{// C ++でそれが発生した場合... //「B」がスローされた場合でも発生する必要があるもの } //「B」がスローされた場合は実行されません。`私見、例外のポイントはエラー処理コードを減らすことなので、スローが発生する可能性のある場所でブロックをキャッチすることは逆効果です。これがRAIIが役立つ理由です。自由に適用すると、例外は最上層と最下層で最も重要になります。
毎年恒例の

1
@burlyearlyあなたの意見は聖ではありませんが、私は要点を理解しますが、C ++ではそのようなことはないので、これをこの動作をエミュレートするトップレイヤーと見なす必要があります。
jave.web 2016

投票=コメントしてください:)
jave.web

0

また、RIIAは、例外処理や最終的な機能の代わりとして完全に役立つものではないと思います。ところで、私はまた、RIIAは悪名高いと思います。私はこれらのタイプのクラスを「管理人」と呼び、それらをLOTとして使用します。リソースの初期化も取得もしていない時間の95%は、スコープベースで変更を適用するか、すでにセットアップされているものを取り、それが確実に破棄されるようにします。これは、インターネットに取り憑かれた公式のパターン名であるため、自分の名前の方がいいかもしれないと提案されても悪用されます。

複数をキャッチする必要がある場合にすべてをクリーンアップするときに複雑さを回避するために、いくつかのアドホックリストの複雑な設定すべてに、それを含むようにクラスを作成する必要があることを要求するのが妥当だとは思わないプロセスで問題が発生した場合の例外タイプ。これは、他の方法では必要のない多くのアドホッククラスにつながります。

はい、特定のリソースを管理するように設計されているクラスや、類似したリソースのセットを処理するように設計されている一般的なクラスは問題ありません。しかし、関係するすべてのものにそのようなラッパーがある場合でも、クリーンアップの調整は、デストラクターの逆順の呼び出しだけではない場合があります。

C ++がfinalを持つことは完全に理にかなっていると思います。つまり、ジーズ、過去10年間に多くの小片やボブがそれにくっついて、奇妙な人々が最後に何かのように突然保守的になり、非常に便利でおそらく他のいくつかのものほど複雑ではないように思える追加されました(ただし、これは私の推測です)。


-2
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}

35
かわいいイディオムですが、まったく同じではありません。tryブロックまたはcatchで戻ると、「finally:」コードを通過しません。
エドワードKMETT 2010

10
エドワードクメットは非常に重要な区別をするため、この誤った答え(評価0)を保持する価値があります。
Mark Lakata

12
さらに大きな欠陥(IMO):このコードはすべての例外を食べますが、実際には起こりfinallyません。
Ben Voigt 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.