310

C++ は ' finally ' ブロックをサポートしていますか?

RAIIイディオムとは何ですか?

C++ の RAII イディオムとC# の「using」ステートメントの違いは何ですか?

4

16 に答える 16

297
于 2008-10-02T07:14:22.897 に答える
83

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

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

また、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

于 2008-10-02T07:47:35.033 に答える
34

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

つまり、RAII の涅槃に到達し、クラスのすべてのメンバーが (スマート ポインターのように) RAII を使用する場合、所有者クラスの非常に単純な (おそらくデフォルトの) dtor で問題を解決できます。メンバー リソースの有効期間。

于 2008-10-02T07:28:26.560 に答える
31

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

実際、ガベージ コレクターに基づく言語には、「最終的に」さらに多くの機能が必要です。ガベージ コレクターはオブジェクトをタイムリーに破棄しないため、メモリに関連しない問題を正しくクリーンアップするために信頼することはできません。

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

でも...

RAII は、例外安全性の責任をオブジェクトのユーザーから設計者に移します。

悲しいことに、これはそれ自体の没落です。古い C プログラミングの習慣はなかなか消えません。C で書かれたライブラリや非常に C スタイルのライブラリを使用している場合、RAII は使用されていません。APIフロントエンド全体を書き直すことはできませんが、それはあなたが取り組まなければならないことです。 次に、「最後に」の欠如は本当に噛みつきます。

于 2010-09-21T11:31:56.590 に答える
11

C++11 ラムダ関数を使用した別の "finally" ブロック エミュレーション

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 :(

"finally" ブロックが 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

コードにfinallyブロックが必要な場合は、スコープ ガードまたは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(); });

    ...
于 2017-11-30T13:14:14.417 に答える
7

古いスレッドを掘り起こして申し訳ありませんが、次の推論には重大な誤りがあります。

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

多くの場合、動的に割り当てられたオブジェクト、オブジェクトの動的な数などを処理する必要があります。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();
  }
}

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

ヒント: "finally" でできることは、メモリの割り当て解除だけではありません。

于 2010-06-02T14:25:48.177 に答える
6

FWIW、Microsoft Visual C++ は try,finally をサポートしており、クラッシュの原因となる重大な例外をキャッチする方法として、歴史的に 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 デバッグ設定は、このメカニズムを壊します。

于 2008-10-02T07:25:09.657 に答える
4

フローの観点から読みやすいと思うので、C++11 言語の一部として完全に受け入れられるfinally べきだと思うユースケースがあります。私の使用例は、スレッドのコンシューマ/プロデューサー チェーンであり、実行の最後にセンチネル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);
        }
    }

これは、ループが終了した後に発生するため、finally 宣言をループの先頭に置くよりも論理的だと思いますが、C++ では実行できないため、これは希望的観測です。キューは別のスレッドに接続されているため、この時点で破棄できないため、のデストラクタにdownstreamセンチネルを配置できないことに注意してください...他のスレッドが.push(nullptr)downstreamnullptr

したがって、ラムダで 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);
        }
    }
于 2015-12-01T01:01:15.780 に答える
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. の後に少なくとも 1 つのcatch()ブロックがtry必要です。これは C++ の要件です。
  3. 関数が void 以外の戻り値を持ち、tryとブロック内に戻り値がない場合、マクロが を返すコードに展開されるcatch()'sため、コンパイルは失敗します。これは、ある種のマクロを使用することによって無効化される可能性があります。finallyvoidfinally_noreturn

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

于 2016-08-01T14:32:14.543 に答える
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 の finally ブロックとまったく同じ動作です。returnまた、 try&catch ブロック内では使用できません。

于 2014-05-30T19:36:59.797 に答える
1

編集済み

中断/継続/復帰などを行っていない場合は、不明な例外にキャッチを追加して、その背後に常にコードを置くことができます。これは、例外を再スローする必要がない場合でもあります。

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(); 

だから問題は何ですか?

通常、最終的に他のプログラミング言語では、通常は何があっても実行されます(通常は、リターン、ブレーク、続行などに関係なく意味します)。ただし、ある種のシステムは例外です。ただし、exit()プログラミング言語ごとに大きく異なります。たとえば、PHP と Java はその中で終了するだけです。瞬間ですが、Python はとにかく最後に実行され、終了します。

しかし、私が上で説明したコードはそのようには機能しません
=> 以下のコード出力のみ something wrong!:

#include <stdio.h>
#include <iostream>
#include <string>

std::string test() {
    try{
       // something that might throw exception
       throw "exceptiooon!";

       return "fine";
    } catch( ... ){
       return "something wrong!";
    }
    
    return "finally";
}

int main(void) {
    
    std::cout << test();
    
    
    return 0;
}
于 2015-09-02T09:23:09.227 に答える
-2
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}
于 2010-04-23T19:28:19.057 に答える