11

finallyC ++にはないため、コードを例外安全にする場合は、代わりにRAIIデザインパターンを使用する必要があります。これを行う1つの方法は、次のようなローカルクラスのデストラクタを使用することです。

void foo() {
    struct Finally {
        ~Finally() { /* cleanup code */ }
    } finalizer();
    // ...code that might throw an exception...
}

クリーンアップコードを2回記述する必要がないため、これは単純なソリューションよりも大きな利点です。

try {
    // ...code that might throw an exception...
    // cleanup code (no exception)
} catch (...) {
    // cleanup code (exception)
    throw;
}

ローカルクラスソリューションの大きな欠点は、クリーンアップコードでローカル変数に直接アクセスできないことです。したがって、それらにアクセスする必要がある場合は、コードが大幅に肥大化します。

void foo() {
    Task* task;
    while (task = nextTask()) {
        task->status = running;
        struct Finally {
            Task* task;
            Finally(Task* task) : task(task) {}
            ~Finally() { task->status = idle; }
        } finalizer(task);
        // ...code that might throw an exception...
    }
}

だから私の質問は:両方の利点を組み合わせた解決策はありますか?そのため、a)重複するコードを記述する必要がなく、b)task最後の例のように、クリーンアップコード内のローカル変数にアクセスできますが、そのようなコードの膨張はありません。

4

5 に答える 5

16

を定義する代わりにstruct Finally、クラスの関数でクリーンアップ コードを抽出し、TaskLoki のScopeGuardを使用できます。

ScopeGuard guard = MakeGuard(&Task::cleanup, task);

ScopeGuards の詳細については、このDrDobb の記事とこのの記事も参照してください。

于 2008-11-20T11:57:24.257 に答える
9

あなたがやろうとしていることを達成するためのよりクリーンな方法はないと思いますが、あなたの例の「最終的なアプローチ」の主な問題は、懸念の不適切な分離だと思います。

たとえば、関数 foo() は Task オブジェクトの一貫性を担当します。これはめったに良い考えではありません。Task のメソッド自体が、ステータスを適切なものに設定する責任を負う必要があります。

最終的に本当に必要な場合があることに気づきました。あなたのコードは明らかに要点を示す単純な例にすぎませんが、そのようなケースはまれです。そして、まれに、もう少し不自然なコードが受け入れられます。

私が言おうとしているのは、finally コンストラクトが必要になることはめったにないはずだということです。また、そうするいくつかのケースでは、より良い方法を構築するために時間を無駄にしないでください。最終的に必要以上に使用するように促すだけです...

于 2008-11-20T11:35:42.287 に答える
6

それはそれを行うためのそのような醜い方法です:(あなたはJavaから来ていますか?)

この記事を読んでください:
C ++は「finally」ブロックをサポートしていますか?(そして、私が聞き続けているこの「RAII」とは何ですか?)

それは、なぜ最終的にそのような醜い概念であるのか、そしてなぜRIAAがはるかにエレガントであるのかを説明しています。

于 2008-11-20T17:24:35.250 に答える
2

私は通常、次のようなものを使用します。

class Runner {
private:
  Task & task;
  State oldstate;
public:
  Runner (Task &t, State newstate) : task(t), oldstate(t.status); 
  {
    task.status = newstate;
  };

  ~Runner() 
  {
    task.status = oldstate;
  };
};

void foo() 
{
  Task* task;
  while (task = nextTask())
  {
    Runner r(*task, running);
            // ...code that might throw an exception...
  }
}
于 2008-11-24T11:39:15.213 に答える
1

他の人が言ったように、「解決策」は関心のより良い分離です。あなたの場合、なぜタスク変数はそれ自体の後でクリーンアップを処理できないのですか? クリーンアップが必要な場合は、ポインターではなく、RAII オブジェクトにする必要があります。

void foo() {
//    Task* task;
ScopedTask task; // Some type which internally stores a Task*, but also contains a destructor for RAII cleanup
    while (task = nextTask()) {
        task->status = running;
        // ...code that might throw an exception...
    }
}

この場合、スマート ポインターが必要になる場合があります (boost::shared_ptr はデフォルトでポインターを削除しますが、代わりに任意のクリーンアップ作業を実行できるカスタム デリーター関数を指定できます。ポインターの RAII の場合、通常はこれを使用します。欲しいです。

問題は、finally キーワードがないことではなく、RAII を実装できない生のポインターを使用していることです。

しかし、通常、すべてのタイプは、自分自身をクリーンアップする方法を知っている必要があります。例外がスローされたときにスコープ内にあったすべてのオブジェクトの後ではなく (これが最終的に実行され、実行しようとしていたことです)、それ自体の直後です。そして、すべてのオブジェクトがそれを行う場合、大きな包括的な「スコープ内のすべてのオブジェクトの後にクリーンアップする」機能はまったく必要ありません。

于 2008-11-20T16:17:38.193 に答える