13

Javaを何年もやっているので、C++を追跡していません。最終的に、言語定義のC ++例外処理に句が追加されましたか?

Javaのtry/finalを模倣する好ましいイディオムはありますか?

また、JavaのThrowableクラスのように、C ++には、スローされる可能性のあるすべての例外に対する究極のスーパータイプがないことも気になります。

私は書くことができます:

try {
  // do something
} catch(...) {
  // alas, can't examine the exception
  // can only do cleanup code and perhaps rethrow, ala:
  throw;
}

補遺編集:

投票数が最も多かった、つまりデストラクタを使用してクリーンアップを行う回答を受け入れることになりました。もちろん、私自身のコメントから、私がそれに完全に同意しないことは明らかです。ただし、C ++はそれ自体であるため、私が念頭に置いているアプリケーションの取り組みでは、多かれ少なかれ、一般的なコミュニティの慣習に従うように努めます。テンプレートクラスを使用して、クラスデストラクタがまだないリソース(つまり、Cライブラリリソース)をラップし、デストラクタセマンティクスを付与します。

新しい補遺編集:

うーん、最終的には閉鎖機能ではなく、おそらく?ScopeGuardアプローチ(以下の回答の1つを参照)と組み合わせたクロージャーは、任意のアクションとクリーンアップコードの外部スコープコンテキストへのアクセスを使用してクリーンアップを実行する方法になります。クリーンアップは、リソースが開かれているときにクリーンアップブロックを提供するRubyプログラミングで見られるイディオム方式で実行できます。C ++ではクロージャ機能が検討されていませんか?

4

15 に答える 15

26

デストラクタを有効活用する。tryブロックで例外がスローされると、その中で作成されたオブジェクトはすぐに破棄されます(したがって、そのデストラクタが呼び出されます)。

これは、オブジェクトのファイナライザーがいつ呼び出されるかわからないJavaとは異なります。

更新:馬の口から 直接: C ++が「最終的に」構成を提供しないのはなぜですか?

于 2009-02-01T05:10:28.440 に答える
15

私の$.02。私は何年もの間、C# や Java などのマネージ言語でプログラミングを行ってきましたが、スピードのために C++ に切り替えることを余儀なくされました。最初は、ヘッダー ファイルと cpp ファイルにメソッド シグネチャを 2 回書き出さなければならないことが信じられませんでした。また、finally ブロックがなく、ガベージ コレクションがないため、あらゆる場所でメモリ リークが追跡されるのが気に入らなかったのです。まあ、私はそれがまったく好きではありませんでした!

しかし、私が言ったように、私は C++ を使わざるを得ませんでした。だから私はそれを真剣に学ぶことを余儀なくされ、今ではRAIIのようなプログラミングイディオムをすべて理解し、言語のすべての微妙な点などを理解しています. 少し時間がかかりましたが、今では C# や Java と比較して言語がどれほど異なるかがわかります。

最近では、C++ が最高の言語だと思います。はい、私が「もみ殻」と呼んでいるもの (書くのは一見不必要なもの) がもう少しあることは理解できますが、実際にその言語を真剣に使用した後、私はそれについて完全に考えを変えました.

私はいつもメモリリークを抱えていました。コードの分離が嫌いだったので、以前はすべてのコードを .h ファイルに書き込んでいました。なぜそうするのか理解できませんでした。そして、私はいつもばかげた周期的インクルード依存関係になってしまい、さらにヒープしていました。私は C# や Java に夢中でしたが、私にとって C++ は大きな一歩でした。最近、私はそれを理解しています。メモリ リークはほとんどありません。インターフェイスと実装の分離を楽しんでおり、サイクルの依存関係の問題はもうありません。

そして、finally ブロックも見逃せません。正直なところ、私の意見では、catch ブロックでクリーンアップ アクションを繰り返し記述するとあなたが話しているこれらの C++ プログラマーは、ただの悪い C++ プログラマーのように聞こえるだけです。つまり、このスレッドの他の C++ プログラマーは、あなたが言及した問題を抱えているようには見えません。RAII は最終的に冗長性をもたらします。1 つのデストラクタを作成すると、最終的に別のデストラクタを作成する必要がなくなります。少なくともそのタイプにとっては。

敬意を表して、私が思っていることは、私がそうであったのと同じように、あなたは今 Java に慣れているということです。

于 2009-02-01T07:40:00.620 に答える
11

C ++の答えはRAIIです。オブジェクトのデストラクタは、スコープ外になると実行されます。返品によるか、例外によるか、その他何でも。例外を別の場所で処理する場合は、呼び出された関数からハンドラーまでのすべてのオブジェクトが、デストラクタを呼び出されることで適切に破棄されることを確認できます。彼らはあなたのために片付けます。

http://en.wikipedia.org/wiki/Resource_acquisition_is_initializationを読む

于 2009-02-01T05:10:58.877 に答える
10

最終的にC++に追加されたわけではなく、追加される可能性もありません。

C ++がコンストラクタ/デストラクタを使用する方法により、最終的には不要になります。
クリーンアップにcatch(...)を使用している場合は、C++を適切に使用していません。クリーンアップコードはすべてデストラクタに含まれている必要があります。

使用する必要はありませんが、C++にはstd::exceptionがあります。
開発者に例外を使用するように特定のクラスから派生させることは、C++の単純な哲学に反します。また、すべてのクラスがObjectから派生する必要がない理由でもあります。

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

finallyを使用すると、クリーンアップを行うためにデストラクタよりもエラーが発生しやすくなります。
これは、クラスのデザイナ/実装者ではなく、オブジェクトのユーザーにクリーンアップを強制しているためです。

于 2009-02-01T05:11:54.397 に答える
9

わかりました、別の回答投稿で作成したポイントへの回答を追加する必要があります: (これを元の質問に編集した方がはるかに便利なので、下に表示されません。それに対する答え。

すべてのクリーンアップが常にデストラクタで行われる場合、catch ブロックにクリーンアップ コードを記述する必要はありませんが、C++ にはクリーンアップ アクションが行われる catch ブロックがあります。確かに、catch(...) のブロックがあり、そこではクリーンアップ アクションしか実行できません (確かに、ロギングを実行するための例外情報を取得することはできません)。

catch にはまったく別の目的があり、Java プログラマーはそのことに注意する必要があります。finally 句は、「無条件の」クリーンアップ アクション用です。ブロックがどのように終了しても、これを行う必要があります。Catch は条件付きクリーンアップ用です。このタイプの例外がスローされた場合、いくつかの追加アクションを実行する必要があります。

finally ブロックのクリーンアップは、例外がスローされたかどうかに関係なく実行されます。これは、クリーンアップ コードが存在する場合に常に実行したいことです。

本当に?このタイプで常に発生するようにしたい場合(たとえば、データベース接続が終了したら常に閉じたい場合)、一度定義してみませんか? タイプ自体で?データベース接続を使用するたびに try/finally を配置するのではなく、データベース接続を自動的に閉じますか?

それがデストラクタのポイントです。それらは、呼び出し元が考える必要なく、使用されるたびに、各タイプが独自のクリーンアップを処理できることを保証します。

C++ 開発者は、try ブロックから正常に終了したときに発生するコード フローの catch ブロックに表示されるクリーンアップ アクションを繰り返さなければならないことに最初から悩まされてきました。Java と C# のプログラマーは、finally ブロックで 1 回だけ実行します。

いいえ、C++ プログラマーはこれに悩まされたことはありません。Cプログラマーは持っています。そして、C++ にクラスがあることに気付き、自分自身を C++ プログラマーと呼んだ C プログラマーはクラスを持っています。

using私は毎日 C++ と C# でプログラミングを行っていますが、データベース接続やクリーンアップが必要なものを使用するたびに、finally 句 (またはブロック) を指定する必要があるという C# のばかげた主張に悩まされていると感じています。

C++ では、「この型を使い終わったら、これらのアクションを実行する必要がある」ということを一度だけ指定できます。メモリの解放を忘れるリスクはありません。ファイル ハンドル、ソケット、またはデータベース接続を閉じるのを忘れるリスクはありません。私のメモリ、ハンドル、ソケット、およびデータベース接続がそれ自体を行うためです。

型を使用するたびに重複するクリーンアップ コードを記述しなければならないことが、どうして望ましいことでしょうか? 型自体にデストラクタがないために型をラップする必要がある場合は、次の 2 つの簡単なオプションがあります。

  • このデストラクタを提供する適切な C++ ライブラリを探します (ヒント: Boost)
  • boost::shared_ptr を使用してラップし、実行時にカスタム ファンクターを提供して、実行するクリーンアップを指定します。

Java EE アプリ サーバー Glassfish、JBoss などのアプリケーション サーバー ソフトウェアを作成する場合、例外情報をキャッチしてログに記録できるようにする必要があります。または、さらに悪いことに、ランタイムに陥り、アプリケーション サーバーが不意に突然終了します。そのため、考えられるすべての例外に対して包括的な基本クラスを用意することが非常に望ましいのです。そして、C++ にはまさにそのようなクラスがあります。std::例外。

CFront の時代から C++ を、この 10 年間のほとんどで Java/C# を使用してきました。根本的に似たものへのアプローチ方法に、巨大な文化的ギャップがあることは明らかです。

いいえ、あなたは C++ をやったことがありません。CFront、またはクラスを使用した C を実行しました。C++ ではありません。大きな違いがあります。答えを不自由だと言うのをやめれば、自分が知っていると思っていた言語について何かを学べるかもしれません。;)

于 2009-02-01T11:58:10.630 に答える
5

クリーンアップ関数自体は完全に不十分です。彼らは、発生したときにのみ関連する一連の活動を実行することが期待されているという点で、結束力が低い. それらは、実際に何かを行う関数が変更されたときに内部を変更する必要があるという点で、高い結合を持っています。このため、エラーが発生しやすくなっています。

try...finally コンストラクトは、クリーンアップ関数のフレームワークです。これは、お粗末なコードを記述する言語推奨の方法です。さらに、同じクリーンアップ コードを何度も書くことを奨励するため、DRY の原則が損なわれます。

これらの目的には、C++ の方法がはるかに適しています。リソースのクリーンアップ コードは、デストラクタで 1 回だけ記述されます。そのリソースの残りのコードと同じ場所にあるため、まとまりがあります。クリーンアップ コードを無関係なモジュールに配置する必要がないため、結合が削減されます。適切に設計されている場合は、正確に 1 回だけ記述されます。

さらに、C++ の方法はより均一です。スマート ポインターが追加された C++ は、あらゆる種類のリソースを同じ方法で処理しますが、Java はメモリを適切に処理し、他のリソースを解放するための不適切な構造を提供します。

C++ には多くの問題がありますが、これはその 1 つではありません。Java が C++ よりも優れている点はいくつかありますが、これはその 1 つではありません。

Java は、try...finally の代わりに RAII を実装する方法を使用する方がはるかに優れています。

于 2009-02-02T17:44:22.040 に答える
4

解放可能なリソースごとにラッパー クラスを定義する必要がないようにするには、その場で「クリーナー」を作成できるScopeGuard ( http://www.ddj.com/cpp/184403758 ) に興味があるかもしれません。

例えば:

FILE* fp = SomeExternalFunction();
// Will automatically call fclose(fp) when going out of scope
ScopeGuard file_guard = MakeGuard(fclose, fp);
于 2009-02-02T14:29:03.023 に答える
3

最終的に正しく使用することがいかに難しいかの例。

2 つのファイルを開いたり閉じたりします。
ファイルが正しく閉じられていることを保証したい場所。
ファイルは再利用される可能性があるため、GC を待機することはできません。

C++ の場合

void foo()
{
    std::ifstream    data("plop");
    std::ofstream    output("plep");

    // DO STUFF
    // Files closed auto-magically
}

デストラクタがなく、finally 句がある言語。

void foo()
{
    File            data("plop");
    File            output("plep");

    try
    {
        // DO STUFF
    }
    finally
    {
        // Must guarantee that both files are closed.
        try {data.close();}  catch(Throwable e){/*Ignore*/}
        try {output.close();}catch(Throwable e){/*Ignore*/}
    }
}

これは単純な例であり、すでにコードは複雑になっています。ここでは、2 つの単純なリソースのみをマーシャリングしようとしています。しかし、管理する必要のあるリソースの数が増えたり、リソースの複雑さが増したりすると、finally ブロックの使用は、例外が存在する場合に正しく使用することがますます難しくなります。

finally を使用すると、正しい使用法に対する責任がオブジェクトのユーザーに移ります。C++ によって提供されるコンストラクター/デストラクター メカニズムを使用することにより、クラスの設計者/実装者に正しい使用の責任を移すことができます。これは、デザイナーがクラス レベルで 1 回だけ正しく実行する必要があるため (さまざまなユーザーがさまざまな方法で正しく実行するのではなく)、継承的に安全です。

于 2009-02-02T16:51:25.687 に答える
2

ラムダ式でC++11 を使用して、最近、次のコードを使用して模倣を開始しましたfinally

class FinallyGuard {
private:
  std::function<void()> f_;
public:
  FinallyGuard(std::function<void()> f) : f_(f) { }
  ~FinallyGuard() { f_(); }
};

void foo() {
  // Code before the try/finally goes here
  { // Open a new scope for the try/finally
    FinallyGuard signalEndGuard([&]{
      // Code for the finally block goes here
    });
    // Code for the try block goes here
  } // End scope, will call destructor of FinallyGuard
  // Code after the try/finally goes here
}

FinallyGuard、呼び出し可能な関数のような引数、できればラムダ式で構築されるオブジェクトです。デストラクタが呼び出されるまで、その関数を記憶するだけです。これは、通常の制御フローまたは例外処理中のスタックの巻き戻しにより、オブジェクトがスコープ外になった場合です。どちらの場合も、デストラクタは関数を呼び出し、問題のコードを実行します。

ブロックのコードのfinally 前にのコードを書かなければならないのは少し奇妙ですが、それを除けば、本物の/ from Javatryのように感じます。独自の適切なデストラクタを持つオブジェクトがより適切な状況でこれを悪用すべきではないと思いますが、上記のアプローチがより適切であると考える場合があります。この質問で、そのようなシナリオの 1 つについて説明しました。tryfinally

私が理解している限りでstd::function<void()>は、ポインタの間接化と少なくとも 1 つの仮想関数呼び出しを使用してその型の消去を実行するため、パフォーマンスのオーバーヘッドが発生します。パフォーマンスが重要なタイト ループでは、この手法を使用しないでください。そのような場合、デストラクタが 1 つのことだけを行う特殊なオブジェクトがより適切です。

于 2013-03-05T13:21:36.277 に答える
1

catch (...)ができるかという点を見逃していると思います。

あなたの例では、「残念ながら、例外を調べることはできません」と言います。例外の種類に関する情報はありません。それがポリモーフィック型であるかどうかさえわからないため、型指定されていない参照があったとしても、安全にdynamic_cast.

何かを行うことができる特定の例外または例外階層について知っている場合、これは明示的に名前が付けられた型を持つ catch ブロックの場所です。

catch (...)C++ ではあまり役に立ちません。スローしないこと、または特定の契約例外のみをスローすることを保証する必要がある場所で使用できます。クリーンアップに使用catch (...)している場合、コードが確実に例外セーフではない可能性が非常に高くなります。

他の回答で述べたように、リソース (RAII) を管理するためにローカル オブジェクトを使用している場合、必要な catch ブロックの数が驚くほど多く、多くの場合、ローカルで例外を除いて何もする必要がない場合でも、リソースの問題がないことを保証しながら、例外に応答できるクライアント コードに例外を流すため、try ブロックは冗長になる可能性があります。

元の質問に答えるために、ブロックの終わり、例外、または例外なしで実行するコードが必要な場合は、レシピになります。

class LocalFinallyReplacement {
    ~LocalFinallyReplacement() { /* Finally code goes here */ }
};
// ...
{ // some function...
    LocalFinallyReplacement lfr; // must be a named object

    // do something
}

trycatchおよびを完全になくす方法に注意してくださいthrow

"finally" ブロックでアクセスする必要がある try ブロックの外側で最初に宣言された関数内のデータがある場合、それをヘルパー クラスのコンストラクターに追加し、デストラクタまで格納する必要がある場合があります。ただし、この時点で、ローカル リソース処理オブジェクトの設計を変更することで問題を解決できるかどうかを真剣に再考します。

于 2009-02-01T11:13:25.480 に答える
1

完全にオフトピックではありません。

Java でのボイラーめっき DB リソースのクリーンアップ

皮肉モード: Java のイディオムは素晴らしいと思いませんか?

于 2009-02-02T18:57:24.030 に答える
1

C++ デストラクタはfinally冗長になります。クリーンアップ コードを finally から対応するデストラクタに移動することで、同じ効果を得ることができます。

于 2009-02-01T06:49:22.610 に答える
0

これに独自のソリューションを追加すると思いました-RAII以外のタイプを処理する必要がある場合の一種のスマートポインターラッパーです。

次のように使用します。

Finaliser< IMAPITable, Releaser > contentsTable;
// now contentsTable can be used as if it were of type IMAPITable*,
// but will be automatically released when it goes out of scope.

それでは、ファイナライザーの実装を次に示します。

/*  Finaliser
    Wrap an object and run some action on it when it runs out of scope.
    (A kind of 'finally.')
    * T: type of wrapped object.
    * R: type of a 'releaser' (class providing static void release( T* object )). */
template< class T, class R >
class Finaliser
{
private:
    T* object_;

public:
    explicit Finaliser( T* object = NULL )
    {
        object_ = object;
    }

    ~Finaliser() throw()
    {
        release();
    }

    Finaliser< T, R >& operator=( T* object )
    {
        if (object_ != object && object_ != NULL)
        {
            release();
        }
        object_ = object;

        return *this;
    }

    T* operator->() const
    {
        return object_;
    }

    T** operator&()
    {
        return &object_;
    }

    operator T*()
    {
        return object_;
    }

private:
    void release() throw()
    {
        R::release< T >( object_ );
    }
};

...そしてここにリリーサーがあります:

/*  Releaser
    Calls Release() on the object (for use with Finaliser). */
class Releaser
{
public:
    template< class T > static void release( T* object )
    {
        if (object != NULL)
        {
            object->Release();
        }
    }
};

free() 用や CloseHandle() 用など、いくつかの異なる種類のリリーサーがあります。

于 2009-03-13T09:42:19.643 に答える
0

私はこの 15 年間、C++ で多くのクラス設計とテンプレート ラッパー設計を行ってきましたが、デストラクタのクリーンアップに関してはすべて C++ の方法で行いました。ただし、すべてのプロジェクトには、オープン、使用、クローズの使用モデルでリソースを提供する C ライブラリの使用も常に含まれていました。try/finally は、そのようなリソースを必要な場所で (完全に堅牢な方法で) 消費し、処理を完了することができることを意味します。その状況をプログラミングするための最も退屈なアプローチ。そのクリーンアップのロジック中に進行中の他のすべての状態を、一部のラッパー デストラクタで範囲を限定せずに処理できます。

私はほとんどの C++ コーディングを Windows で行ったので、そのような状況ではいつでも Microsoft の __try/__finally を使用することができました。(彼らの構造化された例外処理には、例外と対話するための強力な機能があります。) 残念ながら、C 言語は移植可能な例外処理構造を承認したことがないようです。

ただし、どちらのスタイルの例外もスローされる可能性がある try ブロックで C と C++ のコードをブレンドするのは簡単ではなかったため、これは理想的なソリューションではありませんでした。C++ に追加された finally ブロックは、これらの状況に役立ち、移植性を可能にします。

于 2009-02-01T17:01:21.783 に答える