C ++クラスコンストラクターでエラーが発生した場合の対処方法


21

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

私の質問は、

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

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

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


22
コンストラクター内から例外をスローできます。これは完全に有効なパターンです。
アンディ

1
おそらくGoFの作成パターンのいくつかを見ておくべきでしょう。工場出荷時のパターンをお勧めします。
SpaceTrucker

2
#1の一般的な例は、データ検証です。あなたはクラスを持っている場合、IEはSquare、一つのパラメータ、辺の長さを取るコンストラクタで、あなたはその値が0以上であるかどうかを確認したい
デイヴィッドは回復モニカ言う

1
最初の質問については、コンストラクターで仮想関数が非直感的に動作する可能性があることを警告します。解体者も同様です。そのような呼び出しに注意してください。

1
#3-なぜNULLを返したいのですか?OOの利点の1つは、戻り値をチェックする必要がないことです。適切な潜在的な例外を単にcatch()してください。
-MrWonderful

回答:


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

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

  3. いや


4
コンストラクター引数の一部が要件を満たしていないためにエラーとしてマークされているにもかかわらず、オブジェクトがまだ有効な状態でない限り、2)は実際には推奨されません。オブジェクトが有効な状態で存在するか、まったく存在しない場合に適しています。
アンディ

@DavidPacker Agreed、こちらをご覧ください:stackoverflow.com/questions/77639/…ただし、一部のコーディングガイドラインでは、コンストラクターにとって問題となる例外を禁止しています。
セバスチャンレッド

どういうわけか、私はあなたにその答えに対する賛成票を既に与えました、セバスチャン 面白い。:D-
アンディ

10
@ooxiいいえ、そうではありません。オーバーライドされたnewはメモリを割り当てるために呼び出されますが、コンストラクターへの呼び出しは、オペレーターが戻った後にコンパイラーによって行われます。つまり、エラーをキャッチすることはできません。これは、newがまったく呼び出されることを前提としています。スタックに割り当てられたオブジェクト用ではありません。ほとんどのオブジェクトに必要です。
セバスチャンレッド

1
#1の場合、RAIIは一般的な例であり、コンストラクターでより多くの処理が必要になる場合があります。
エリック

20

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

オブジェクトのこの構築がどのように行われるかに応じて、非静的メソッドでオブジェクトの構築を可能にする別のオブジェクトを作成することをお勧めします。

コンストラクターを間接的に呼び出すことは、多くの場合「ファクトリー」と呼ばれます。

これにより、nullオブジェクトを返すこともできます。これは、nullを返すよりも優れたソリューションになる可能性があります。


ありがとう@null!残念ながら、ここで2つの答えを受け入れることはできません:(そうでなければ、私はあまりにもこの答えを受け入れているだろう!!おかげで再び!
MayurK

@MayurKの心配はありません、受け入れられた答えは正しい答えをマークすることではなく、あなたのために働いたものです。
nullの

3
@null:C ++では、単に返すことはできませんNULL。たとえば、int foo() { return NULL実際0には整数オブジェクトである(ゼロ)を返します。ではstd::string foo() { return NULL; }、あなた誤って呼びたいstd::string::string((const char*)NULL)未定義の動作(NULLが\ 0で終わる文字列を指していない)です。
–MSalters

3
std :: optionalは遠いかもしれませんが、そのようにしたい場合は、常にboost :: optionalを使用できます。
ショーンバートン

1
@Vld:C ++では、オブジェクトはクラス型に制限されません。そして、一般的なプログラミングでは、最終的にで工場が完成することも珍しくありませんint。たとえばstd::allocator<int>、完全に健全な工場です。
–MSalters

5

@SebastianRedlはすでに簡単で直接的な回答を提供しましたが、いくつかの追加の説明が役立つかもしれません。

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


これは、コンストラクターが定義済みの有効な状態を設定するためにできる限り最小限のことを行う必要があるという一般的なスタイルの規則です。初期化がより複雑な場合は、コンストラクタの外部で処理する必要があります。コンストラクターが設定できる初期化するための安価な値がない場合は、クラスによって適用される不変式を弱めて、それを追加する必要があります。たとえば、管理するクラスにストレージを割り当てるのが高すぎる場合は、nullのような特別な場合の状態が問題を引き起こすことはないため、未割り当てのnull状態を追加します。エヘム。

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

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

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

ただし、既に正常に構築されたオブジェクトのコンポーネントは、すでにコンパイラーの責任です。これは、実際には大した問題ではないことを意味します。例えば

classname (args) : base1 (args), member2 (args), member3 (args)
{
}

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

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

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

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


「まだコンストラクター呼び出し中ですが、オブジェクトは必ずしも完全に構築されているわけではありません。そのため、そのオブジェクトのデストラクターを呼び出すことは有効ではありません。したがって、オブジェクトを破壊する責任はコンパイラーに移ります。コンストラクタが正常に完了したとき」は、コンストラクタの委任に関する更新を実際に使用できます。オブジェクトは、最も派生したコンストラクターが完了すると完全に構築され、委任コンストラクター内で例外が発生すると、デストラクター呼び出されます。
ベンフォークト

したがって、「do-the-minimum」コンストラクターはプライベートにすることができ、「make_whatever()」関数はプライベートコンストラクターを呼び出す別のコンストラクターにすることができます。
ベンフォークト

これは、私がよく知っているRAIIの定義ではありません。RAIIについての私の理解は、オブジェクトのコンストラクター内(でのみ)に意図的にリソースを取得し、そのデストラクターでリソースを解放することです。このようにして、オブジェクトをスタックで使用して、カプセル化するリソースの取得と解放を自動的に管理できます。典型的な例は、構築時にミューテックスを取得し、破壊時にミューテックスを解放するロックです。
エリック

1
@Eric-はい、それは絶対に標準的なプラクティスです-一般的にRAIIと呼ばれる標準的なプラクティスです。定義を拡張するのは私だけではありません-一部の講演では、Stroustrupでさえあります。はい、RAIIは、リソースライフサイクルをオブジェクトライフサイクルにリンクすることに関するもので、メンタルモデルは所有権です。
Steve314

1
@Eric-以前の返信は、説明が不適切だったため削除されました。とにかく、オブジェクト自体は所有できるリソースです。すべてのものにmain関数または静的/グローバル変数までチェーンで所有者が必要です。使用して割り当てられたオブジェクトがnewされていないあなたはその責任を割り当てるまで所有していますが、スマートポインタは、彼らが参照するヒープ割り当てられたオブジェクトを所有し、コンテナはそのデータ構造を所有しています。所有者は早期に削除することを選択でき、所有者のデストラクタが最終的な責任を負います。
Steve314
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.