6

Bjarne Stroustrup は、彼のC++ Style and Technique FAQに書いています。

C++ は、ほとんど常により優れた代替手段をサポートしているため、「リソース取得は初期化」手法 (TC++PL3 セクション 14.4) です。基本的な考え方は、ローカル オブジェクトのデストラクタがリソースを解放するように、ローカル オブジェクトによってリソースを表すことです。そうすれば、プログラマーはリソースの解放を忘れることはありません。例えば:

class File_handle {
    FILE* p;
public:
    File_handle(const char* n, const char* a)
        { p = fopen(n,a); if (p==0) throw Open_error(errno); }
    File_handle(FILE* pp)
        { p = pp; if (p==0) throw Open_error(errno); }

    ~File_handle() { fclose(p); }

    operator FILE*() { return p; }

    // ...
};

void f(const char* fn)
{
    File_handle f(fn,"rw"); // open fn for reading and writing
    // use file through f
}

システムでは、リソースごとに「リソース ハンドル」クラスが必要です。ただし、リソースを取得するたびに「finally」句を付ける必要はありません。実際のシステムでは、リソースの種類よりもはるかに多くのリソースの取得が行われるため、「リソースの取得は初期化です」という手法は、「最終的に」構成を使用するよりもコードが少なくて済みます。

Bjarne は、「常により良い」ではなく、「ほぼ常により良い」と書いていることに注意してください。さて、私の質問ですがfinally、C++ で代替コンストラクト (RAII) を使用するよりもコンストラクトの方が優れているのはどのような状況でしょうか?

4

6 に答える 6

7

それらの違いは、デストラクタは、使用されている型に関連付けることによってクリーンアップ ソリューションの再利用を強調するのに対し、try/finally は 1 回限りのクリーンアップ ルーチンを強調することです。そのため、try/finally は、使用している型に関連付けることができる再利用可能なクリーンアップ ソリューションではなく、使用ポイントに関連付けられた一意の 1 回限りのクリーンアップ要件がある場合にすぐに便利です。

私はこれを試していません (最近の gcc を何ヶ月もダウンロードしていません) が、それは本当のはずです: 言語にラムダを追加することで、C++ はfinallyと呼ばれる関数を書くだけで、 と同等の効果を持つことができるようになりましたtry_finally。明らかな使用法:

try_finally([]
{
    // attempt to do things in here, perhaps throwing...
},
[]
{
    // this always runs, even if the above block throws...
}

もちろん、 try_ を書かなければfinallyなりませんが、一度だけでいいのです。ラムダは新しい制御構造を可能にします。

何かのようなもの:

template <class TTry, class TFinally>
void try_finally(const TTry &tr, const TFinally &fi)
{
    try
    {
        tr();
    }
    catch (...)
    {
        fi();
        throw;
    }

    fi();
}

また、GC の存在と、デストラクタの代わりに try/finally を優先することとの間には、まったく関連性がありません。C++/CLI にはデストラクタと GC があります。それらは直交する選択です。try/finally とデストラクタは、同じ問題に対するわずかに異なるソリューションであり、どちらも決定論的であり、代替不可能なリソースに必要です。

C++ 関数オブジェクトは再利用性を強調しますが、1 回限りの無名関数は苦痛になります。ラムダを追加することで、無名コード ブロックを簡単に作成できるようになり、名前付き型によって表現される「強制的な再利用性」に対する C++ の従来の強調が回避されます。

于 2008-12-21T22:33:32.913 に答える
6

最後に、C コードで接続する場合はより良いでしょう。既存の C 機能を RAII でラップしなければならないのは面倒なことです。

于 2008-12-21T22:19:34.610 に答える
6

finally ブロックが「より良い」と考えることができる唯一の理由は、同じことを達成するのに必要なコードが少ない場合です。たとえば、何らかの理由で RAII を使用しないリソースがある場合、リソースをラップしてデストラクタで解放するクラスを作成するか、finally ブロック (存在する場合) を使用する必要があります。

比較:

class RAII_Wrapper
{
    Resource *resource;

public:
    RAII_Wrapper() : resource(aquire_resource()) {}

    ~RAII_Wrapper() {
        free_resource(resource);
        delete resource;
    }

    Resource *getResource() const {
        return resource;
    }
};

void Process()
{
    RAII_Resource wrapper;
    do_something(wrapper.resource);
}

対:

void Process()
{
    try {
        Resource *resource = aquire_resource();
        do_something(resource);
    }
    finally {
        free_resource(resource);
        delete resource;
    }
}

ほとんどの人 (私を含む) は、try...finally ブロックの使用を強制しないため、最初のバージョンの方が優れていると主張します。また、リソースを使用するすべての関数でコードを複製する必要はなく、クラスを 1 回記述するだけで済みます。

編集:前述の litb のように、ポインターを手動で削除する代わりに auto_ptr を使用する必要があります。これにより、両方のケースが簡素化されます。

于 2008-12-21T22:31:46.177 に答える
3

スコープガードは、複数の流路をうまく処理できるため、より一般的な意味では優れている一方で、最終的にうまく処理される1回限りのケースをうまく処理できると思います。

于 2008-12-22T14:45:25.383 に答える
0

6回の回答後に編集します。

これはどうですか:

class Exception : public Exception { public: virtual bool isException() { return true; } };
class NoException : public Exception { public: bool isException() { return false; } };


Object *myObject = 0;

try
{
  try
  {
    myObject = new Object(); // Create an object (Might throw exception)
  }
  catch (Exception &e)
  {
    // Do something with exception (Might throw if unhandled)
  }

  throw NoException();
}
catch (Exception &e)
{
  delete myObject;

  if (e.isException()) throw e;
}
于 2008-12-21T23:19:11.383 に答える