web-dev-qa-db-ja.com

C ++に「最終的に」構成要素がないのはなぜですか?

C++での例外処理は、try/throw/catchに制限されています。 Object Pascal、Java、C#、Pythonとは異なり、C++ 11でも、finally構文は実装されていません。

私は「例外安全なコード」について議論している非常に多くのC++の文献を見てきました。 Lippmanは、例外セーフコードは重要だが高度で困難なトピックであり、彼の入門書の範囲を超えていると書いています。これは、安全なコードがC++の基本ではないことを示唆しているようです。 Herb Sutterは、彼のExceptional C++のトピックに10の章を費やしています。

しかし、「例外安全なコード」を書き込もうとしたときに遭遇する問題の多くは、finally構成が実装されていればかなりうまく解決でき、プログラマーが例外の場合でもそれを保証できるように思えます、プログラムを安全で安定したリークのない状態に復元できます。これは、リソースの割り当てと潜在的に問題のあるコードのポイントに近くなります。私は非常に経験豊富なDelphiおよびC#プログラマーとして、try ..を使用します。最終的に、これらの言語のほとんどのプログラマーと同様に、コード内で非常に広範囲にブロックします。

C++ 11で実装されたすべての「鐘と笛」を考えると、「最終的に」はまだそこにないことに驚きました。

では、なぜfinally構文がC++に実装されていないのですか?把握することは実際にはそれほど難しいまたは高度な概念ではなく、プログラマーが「例外安全なコード」を作成するのを支援するために長い道のりを進んでいます。

60
Vector

それは本当にC++の哲学とイディオムを理解するだけの問題です。永続クラスでデータベース接続を開き、例外がスローされた場合にその接続を確実に閉じる操作の例を見てみましょう。これは例外の安全性の問題であり、例外のあるすべての言語(C++、C#、Delphi ...)に適用されます。

try/finallyを使用する言語では、コードは次のようになります。

database.Open();
try {
    database.DoRiskyOperation();
} finally {
    database.Close();
}

シンプルでわかりやすい。ただし、いくつかの欠点があります。

  • 言語に決定論的デストラクタがない場合、私は常にfinallyブロックを書き込む必要があります。そうしないと、リソースがリークします。
  • DoRiskyOperationが1つ以上のメソッド呼び出しである場合、つまりtryブロックで処理を行う必要がある場合、Close操作は、Open操作から少し離れている可能性があります。買収のすぐ隣に整理を書くことができません。
  • 取得して例外セーフな方法で解放する必要があるいくつかのリソースがある場合、try/finallyブロックの深い複数のレイヤーになる可能性があります。

C++のアプローチは次のようになります。

ScopedDatabaseConnection scoped_connection(database);
database.DoRiskyOperation();

これにより、finallyアプローチの欠点がすべて解決されます。それにはいくつかの欠点がありますが、比較的軽微です。

  • ScopedDatabaseConnectionクラスを自分で作成する必要がある可能性は十分にあります。ただし、これは非常に単純な実装であり、コードは4行または5行しかありません。
  • 「デストラクタに依存して混乱をクリーンアップするためにクラスを絶えず作成および破棄することは非常に貧弱である」というコメントに基づいて、追加のローカル変数を作成する必要があります。追加のローカル変数が関与する追加の作業のいずれか。優れたC++設計は、この種の最適化に大きく依存しています。

個人的には、これらの長所と短所を考慮して、RAII (Resource Acquisition Is Initialization) は、finallyよりもはるかに望ましい手法です。あなたのマイレージは異なる場合があります。

最後に、RAIIはC++で非常に定評のあるイディオムであるため、開発者が多数のScoped...クラスを作成する負担を軽減するために、 ScopeGuard および Boost.ScopeExit この種の確定的なクリーンアップを容易にします。

58
Josh Kelley

From C++が「最終的に」構成を提供しないのはなぜですか? Bjarne StroustrupのC++スタイルとテクニックに関するFAQ

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

58

C++にfinallyがないのは、C++では必要ないためです。 finallyは、例外が発生したかどうかに関係なく、一部のコードを実行するために使用されます。ほとんどの場合、これは一種のクリーンアップコードです。 C++では、このクリーンアップコードは関連クラスのデストラクタにある必要があり、デストラクタは常にfinallyブロックのように呼び出されます。クリーンアップにデストラクタを使用するイディオムは [〜#〜] raii [〜#〜] と呼ばれます。

C++コミュニティ内では、「例外セーフ」コードについてもっと多くの話があるかもしれませんが、例外がある他の言語でもほぼ同じくらい重要です。 「例外セーフ」コードの要点は、呼び出す関数/メソッドのいずれかで例外が発生した場合に、コードがどのような状態になるかを考えることです。
C++では、例外のために孤立したままになっているオブジェクトを処理する自動ガベージコレクションがC++にないため、「例外セーフ」コードの方が少し重要です。

例外の安全性がC++コミュニティでさらに議論される理由は、言語のデフォルトのセーフティネットの数が少ないため、C++では何がうまくいかないかをより多く認識しなければならないという事実におそらく起因します。

他の人は解決策としてRAIIを議論しました。それは完全に良い解決策です。しかし、それは実際には対処しませんなぜfinallyも追加しませんでした。これに対する答えは、C++の設計と開発のより基本的なものです。C++の開発全体を通じて、関係者は、大規模な手間をかけずに他の機能を使用して実現できる設計機能の導入に強く抵抗してきました。古いコードを互換性のないものにする可能性のある新しいキーワードの。 RAIIはfinallyに代わる非常に機能的な手段を提供し、実際にとにかくC++ 11で独自のfinallyをロールバックできるため、呼び出しはほとんどありませんでした。

デストラクタでコンストラクタに渡された関数を呼び出すクラスFinallyを作成するだけです。次に、これを行うことができます:

try
{
    Finally atEnd([&] () { database.close(); });

    database.doRisky();
}

ただし、ほとんどのネイティブC++プログラマーは、一般に、きれいに設計されたRAIIオブジェクトを好みます。

13
Jack Aidley

Try/catchブロックを使用したくない場合でも、「トラップ」パターンを使用できます。

必要なスコープに単純なオブジェクトを配置します。このオブジェクトのデストラクタに「最終」ロジックを入れます。何があっても、スタックがほどかれると、オブジェクトのデストラクタが呼び出され、キャンディーが手に入ります。

3
Aryéh Radlé

ええと、Lambdasを使用して独自のfinallyを並べ替えることができます。これにより、次のコードが正常にコンパイルされます(もちろん、最も優れたコードではなく、RAIIのない例を使用します)。

{
    FILE *file = fopen("test","w");

    finally close_the_file([&]{
        cout << "We're closing the file in a pseudo-finally clause." << endl;
        fclose(file);
    });
}

この記事 を参照してください。

2
einpoklum