web-dev-qa-db-ja.com

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

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

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

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

249
Kevin

いいえ、C++は「最終」ブロックをサポートしていません。その理由は、C++が代わりにRAIIをサポートしているためです:「リソース獲得は初期化」-poor name 本当に便利なコンセプトです。

アイデアは、オブジェクトのデストラクタがリソースを解放する責任があるということです。オブジェクトに自動保存期間がある場合、オブジェクトのデストラクタは、作成されたブロックが終了するときに呼び出されます-例外が存在する場合でもそのブロックが終了する場合です。トピックの 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がメモリを含むあらゆるタイプのリソースを確定的に解放することです。 IDisposableを.NET(.NET言語C++/CLIを含む)に実装すると、メモリを除いてリソースが確定的に解放されます。 .NETでは、メモリは確定的に解放されません。メモリはガベージコレクションサイクル中にのみ解放されます。

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

252
Kevin

C++では、最後に ない RAIIのために必要です。

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

74
Martin York

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ファイル記述子を確実に閉じました。

通常、リソースを管理し、あらゆる種類のリークを回避する実際のクラスを用意する方が適切ですが、このfinallyは、クラスが次のように聞こえる場合に役立ちますやり過ぎ。

その上、私は他の言語よりも好きですfinally自然に使用する場合、オープニングコードの近くにクロージングコードを書く(私の例ではnewおよびdelete)および破棄はLIFOの順序で構築されますC++では通常。唯一の欠点は、実際に使用しない自動変数を取得し、ラムダ構文が少しうるさいことです(私の例では、4行目のWordfinallyおよび右側の{}ブロックは意味があり、残りは本質的にノイズです)。

もう一つの例:

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

disableメンバーは、finallyのみで呼び出す必要がある場合に役立ちます失敗の場合。たとえば、3つの異なるコンテナにオブジェクトをコピーする必要があります。finallyを設定して、各コピーを取り消し、すべてのコピーが成功した後に無効にすることができます。そうすることで、破壊がスローできない場合、強力な保証を保証します。

disable例:

//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"; }
54
Paolo.Bolzoni

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

これは、RAII nirvanaに到達し、クラスのすべてのメンバーがRAIIを使用する場合(スマートポインターなど)、所有者クラスの手動で管理する必要がないため、所有者クラスの非常に単純な(おそらくデフォルトの)dtorで逃げることができることを意味しますメンバーリソースのライフタイム。

30
Michael Burr

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

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

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

しかしながら...

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

悲しいことに、これはそれ自身の没落です。古いCプログラミングの習慣は一生懸命死にます。 Cまたは非常にCスタイルで記述されたライブラリを使用している場合、RAIIは使用されていません。 APIフロントエンド全体を書き直すのではなく、それだけで作業する必要があります。 その後「最終的に」実際に噛まれない。

30
Philip Couling

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

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

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

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

代わりに、youいルートに行く必要があります。

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

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

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

7
Mephane

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

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

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

PS

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

    ...
7
anton_rh

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

そのため、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の使用は、標準化された機能の使用とほぼ同じです。

5
tobi_s

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デバッグ設定はこのメカニズムを破壊します。

5
SmacL

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

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;

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

3
bcmpinc

使用できるfinallyマクロを思い付きましたほぼ同じ¹Javaのfinallyキーワード。 std::exception_ptrとそのフレンド、ラムダ関数、およびstd::promiseを使用するため、C++11以上が必要です。また、 複合ステートメント式 GCC拡張も使用します。これはclangでもサポートされています。

WARNING以前のバージョン この回答では、より多くの制限がある概念の異なる実装が使用されました。

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

#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から必要な機能のみを再実装することで回避できる、かなりの不要なオーバーヘッドも発生します。


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

  1. breakおよびcatch()のブロック内からtryステートメントを使用して外側のループを中断することはできません。これらはラムダ関数内にあるためです。
  2. tryの後に少なくとも1つのcatch()ブロックが必要です。これはC++の要件です。
  3. 関数にvoid以外の戻り値があるが、tryおよびcatch()'sブロック内に戻りがない場合、finallyマクロはvoidを返すコードに展開されるため、コンパイルは失敗します。これは、finally_noreturnマクロを並べることで、avoidedになる可能性があります。

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

3
Fabio A.

finallyshouldが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は別のスレッドに接続されているため、この時点では破棄できないため、downstreamのデストラクタにセンチネルPush(nullptr)を入れることはできません...他のスレッドが受信するまで生き続ける必要があります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);
        }
    }
2
Mark Lakata

多くの人が述べているように、解決策はC++ 11の機能を使用してfinally blockを回避することです。機能の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を使用するためのもう1つの紹介は、 here です。

1
Mark Lakata

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

最終ブロックを常に呼び出す場合は、最後の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割り当てを設定し、変数のキャッチブロックチェック後に値:

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();
}
1
jave.web