A :: A(int x):b(x){}
またはここに保存します:
A :: A(int x){b = new B(x); }
それぞれのアプローチには長所と短所がありますが、この記事では例外処理の問題に焦点を当てたいと思います。
順番に始めましょう。 特定のクラスがあり、そのコンストラクターが例外をスローする場合があるとします(ファイルなし、接続なし、パスワードなし、操作を実行するための十分な権限がないなど)。 私たちのクラスは非常にシンプルで予測可能です。
クラスX { プライベート: int xx; 公開: X(int x){ cout << "X :: X x =" << x << endl; if(x == 0)throw(exception()); xx = x; } 〜X(){ cout << "X ::〜X x =" << xx << endl; } };
つまり、コンストラクター引数がゼロの場合、例外がスローされます。
特定のクラスが必要で、そのオブジェクトにはクラスXの2つのオブジェクトが含まれている必要があるとします。
オプション1-ポインター付き(注意、危険なコード!)
科学のすべてを行います:
クラスCnt { プライベート: X * xa; X * xb; 公開: Cnt(int a、int b){ cout << "Cnt :: Cnt" << endl; xa =新しいX(a); xb =新しいX(b); } 〜Cnt(){ cout << "Cnt ::〜Cnt" << endl; xaを削除します。 xbを削除します。 } };
彼らは何も忘れていないようです。 (もちろん、厳密に言えば、少なくとも、ポインターで正しく機能するコピーコンストラクターと割り当て操作を忘れていました。まあ、大丈夫です。)
このクラスを使用します。
{ Cnt c(1、0); } catch(...){ cout << "error" << endl; }
そして、いつ、いつ、それが構築され、破壊されるかを把握します。
- まず、Cntオブジェクト作成プロセスが開始されます。
- オブジェクト* xaが作成されます
- * xbオブジェクトの作成が始まります...
- ...そして例外があります
それだけです コンストラクターは作業を停止し、Cntオブジェクトのデストラクタは呼び出されません(当然、オブジェクトは作成されていません)。 合計、何がありますか? 1つのオブジェクトX、(xa)が永久に失われるポインター。 この場所では、すぐにメモリリークが発生し、おそらくより貴重なリソース、ソケット、カーソルのリークが発生します...
これは最も不快な状況の1つであり、常に特定の引数(最初の引数がゼロではなく、2番目の引数がゼロ)でのみリークが発生するわけではないことに注意してください。 そのような漏れを見つけることは非常に困難です。
明らかに、そのようなソリューションは、非常に単純なプログラムにのみ適しています。例外が発生した場合、単純に無力になり、それだけです。
解決策は何ですか?
最も単純で、最も信頼性が高く、最も自然なソリューションは、値によってオブジェクトを保存することです。
例:
クラスCnt { プライベート: X xa; X xb; 公開: Cnt(int a、int b):xa(a)、xb(b){ cout << "Cnt :: Cnt" << endl; } 〜Cnt(){ cout << "Cnt ::〜Cnt" << endl; } };
それはコンパクトで、エレガントで、自然です...しかし、主なことは安全です! この場合、コンパイラは発生するすべてを監視し、(可能であれば)不要になったすべてをクリーンアップします。
コードの結果:
{ Cnt c(1、0); } catch(...){ cout << "error" << endl; }
このようになります:
X :: X x = 1 X :: X x = 0 X ::〜X x = 1 エラー
つまり、Cnt :: xaオブジェクトは自動的に正しく破棄されました。
ポインターを使用したクレイジーな決定
次の悪夢は本当の悪夢になります。
Cnt(int a、int b){ cout << "Cnt :: Cnt" << endl; xa =新しいX(a); { xb =新しいX(b); } catch(...){ xaを削除します。 投げる } }
Cnt :: xcが表示されたらどうなるか想像できますか? そして、初期化の順序を変更する必要がある場合は?..そのようなコードに付随して、何も忘れないように多くの努力をする必要があります。 そして、最も厄介なのは、熊手をどこにでも広げたのはあなた自身です。
例外についての叙情的な余談。
なぜ例外が発明されたのですか? プログラムの通常のコースの説明を、いくつかの障害に対する反応の説明から分離するため。
この例では、この美しい教義をひどく踏みにじっています。 例外を処理するコードを、例外の原因となるコードの近くに配置する必要があります。
これは、例外メカニズムの魅力を無効にします。 実際、Cの概念に戻ります。ここでは、各操作の後に、グローバル変数の値またはエラーの他の兆候を確認する必要があります。
これにより、コードが混乱し、理解および保守が困難になります。
リアルインディアンソリューション-スマートポインター
それでもポインターを保存する必要がある場合は、ポインターをラップすればコードを保護できます。 自分で作成することも、既存の多くのものを使用することもできます。 Auto_ptrの例:
クラスCnt { プライベート: auto_ptr <X> ia; auto_ptr <X> ib; 公開: Cnt(int a、int b):ia(新しいX(a))、ib(新しいX(b)){ cout << "Cnt :: Cnt" << endl; } 〜Cnt(){ cout << "Cnt ::〜Cnt" << endl; } };
クラスメンバーを値で格納するという決定にほとんど戻りました。 ここでクラスauto_ptr <X>のオブジェクトを値で保存します。コンパイラはこれらのオブジェクトのタイムリーな削除を再び処理します(デストラクタでdeleteを呼び出す必要はありません)。 そして、それらは順番にXオブジェクトへのポインタを保存し、メモリが時間通りに解放されることを確認します。
はい! 接続することを忘れないでください
#include <メモリ>
auto_ptrテンプレートについて説明しています。
新しいことについての叙情的な余談
C ++のCに対する利点の1つは、C ++を使用すると、通常の変数と同様に、複雑なデータ構造(オブジェクト)を操作できることです。 つまり、C ++はこれらの構造を作成して削除します。 プログラマは、自分(プログラマ)が自分でオブジェクトを作成し始めるまで、リソースを解放することを考えないかもしれません。 「新規」を書いたらすぐに、必要な場所に「削除」を書く義務があります。 そして、これらはデストラクタだけではありません。 さらに、ほとんどの場合、コピー操作と割り当て操作を個別に実装する必要があります。つまり、C ++サービスを拒否し、非常に不安定な状況に陥っています。
もちろん、実際の生活では、しばしば「新しい」を使用する必要があります。 これは、アルゴリズムの仕様によるもの、パフォーマンス要件によって決定されるもの、または単に他の人のインターフェースによって課されるものです。 ただし、選択肢がある場合は、「新しい」という言葉を書く前に、おそらく3回考える必要があります。
すべて成功! そして、あなたの記憶が決して流れないように!