web-dev-qa-db-ja.com

C ++クラスコンストラクターで失敗した場合の処理​​方法

コンストラクターがいくつかの操作を行うCPPクラスがあります。これらの操作の一部は失敗する可能性があります。コンストラクタは何も返さないことを知っています。

私の質問は、

  1. コンストラクターでメンバーを初期化する以外の操作を行うことはできますか?

  2. コンストラクターの一部の操作が失敗したことを呼び出し側の関数に伝えることは可能ですか?

  3. コンストラクターでエラーが発生した場合、new ClassName()でNULLを返すようにできますか?

21
MayurK
  1. はい。ただし、一部のコーディング標準では禁止されている場合があります。

  2. はい。推奨される方法は、例外をスローすることです。または、オブジェクト内にエラー情報を保存し、この情報にアクセスするためのメソッドを提供することもできます。

  3. 番号。

42
Sebastian Redl

計算を実行し、成功した場合はオブジェクトを、失敗した場合はオブジェクトを返す静的メソッドを作成できます。

このオブジェクトの構築方法によっては、非静的メソッドでオブジェクトを構築できる別のオブジェクトを作成する方がよい場合があります。

コンストラクタを間接的に呼び出すことは、「ファクトリ」と呼ばれることがよくあります。

これにより、nullオブジェクトを返すこともできます。これは、nullを返すよりも優れた解決策になる場合があります。

20
null

@SebastianRedlはすでに単純で直接的な回答を提供していますが、追加の説明が役立つ場合があります。

TL; DR =コンストラクターをシンプルに保つためのスタイルルールがあります。その理由はありますが、これらの理由は主に、歴史的な(または単に悪い)コーディングスタイルに関連しています。コンストラクターでの例外の処理は明確に定義されており、完全に構築されたローカル変数とメンバーに対してデストラクターが呼び出されます。つまり、慣用的なC++コードで問題が発生することはありません。とにかくスタイルルールは存続しますが、通常それは問題ではありません。すべての初期化がコンストラクター内にある必要はなく、特にそのコンストラクターである必要はありません。


定義された有効な状態を設定するために、コンストラクターが絶対的な最小値を実行する必要があるのは一般的なスタイルルールです。初期化がより複雑な場合は、コンストラクターの外部で処理する必要があります。コンストラクターがセットアップできる安価な初期化値がない場合は、クラスによって適用される不変条件を弱め、追加する必要があります。たとえば、クラスが管理するためのストレージの割り当てが高すぎる場合は、nullのような特別なケースの状態が原因で問題が発生することはないため、未割り当てのnull状態を追加します。エヘン。

一般的ですが、確かにこの極端な形では、絶対的なものからはほど遠いです。特に、私の皮肉が示すように、私は不変条件を弱めることはほとんどの場合価格が高すぎるというキャンプにいます。ただし、スタイルルールの背後には理由があり、最小限のコンストラクターの強い不変式の両方を使用する方法があります。

その理由は、特に例外に直面した場合の、デストラクタの自動クリーンアップに関連しています。基本的に、コンパイラがデストラクタの呼び出しを担当する時点は明確に定義されている必要があります。まだコンストラクター呼び出しを行っている間、オブジェクトは必ずしも完全に構築されているわけではないため、そのオブジェクトのデストラクターを呼び出すことは無効です。したがって、オブジェクトを破棄する責任は、コンストラクタが正常に完了したときにのみコンパイラに移ります。これはRAII(Resource Allocation Is Initialization)と呼ばれ、実際には最適な名前ではありません。

コンストラクター内で例外のスローが発生した場合、通常は_try .. catch_で、部分的に構築されたものを明示的にクリーンアップする必要があります。

ただし、既に正常に構築されているオブジェクトのcomponentsは、コンパイラの責任です。これは、実際には大したことではないということです。例えば.

_classname (args) : base1 (args), member2 (args), member3 (args)
{
}
_

このコンストラクターの本体は空です。 _base1_、_member2_、および_member3_のコンストラクターが例外セーフである限り、心配する必要はありません。たとえば、_member2_のコンストラクターがスローする場合、そのコンストラクターはそれ自体をクリーンアップする必要があります。ベース_base1_はすでに完全に構​​築されているため、そのデストラクタが自動的に呼び出されます。 _member3_は部分的に構築されることさえなかったので、クリーンアップは必要ありません。

本体がある場合でも、例外がスローされる前に完全に構​​築されたローカル変数は、他の関数と同様に自動的に破棄されます。生のポインタを操作する、または何らかの種類の暗黙的な状態(別の場所に格納)を「所有」するコンストラクタ本体-通常、開始/取得関数呼び出しは終了/解放呼び出しと一致する必要があることを意味します-例外の安全性の問題を引き起こす可能性がありますが、実際の問題クラスを介してリソースを適切に管理できません。たとえば、コンストラクターで生のポインターを_unique_ptr_に置き換えると、必要に応じて_unique_ptr_のデストラクタが自動的に呼び出されます。

最小限のコンストラクタを好む理由はまだ他にもあります。 1つは単にスタイルルールが存在するためであり、多くの人々はコンストラクターの呼び出しが安価であると想定しています。それを実現する1つの方法は、依然として強力な不変量を持ち、代わりに弱められた不変量を持ち、通常のメンバー関数呼び出しを使用して(潜在的に多く)必要な初期値を設定する別個のファクトリー/ビルダークラスを用意することです。必要な初期状態を取得したら、そのオブジェクトを引数として、強い不変式を持つクラスのコンストラクターに渡します。これは、弱不変オブジェクトの「根性を盗む」ことができます-意味論を移動します-これは安価な(そして通常noexcept)操作です。

そしてもちろん、それをmake_whatever ()関数でラップすることができるため、その関数の呼び出し元は、弱められた不変クラスのインスタンスを見る必要はありません。

5
Steve314