244

コンストラクターが例外をスローするのはいつですか? (または Objective C の場合: init'er が nil を返すのはいつですか?)

オブジェクトが完全でない場合、コンストラクターは失敗し、オブジェクトの作成を拒否するように思えます。つまり、コンストラクターは、意味のあるメソッドを呼び出すことができる機能的で動作するオブジェクトを提供するために、呼び出し元と契約を結ぶ必要がありますか? それは合理的ですか?

4

23 に答える 23

330

コンストラクターの仕事は、オブジェクトを使用可能な状態にすることです。これには基本的に2つの考え方があります。

1 つのグループは、2 段階の建設を支持しています。コンストラクターは、オブジェクトをスリープ状態にして、作業を拒否するだけです。実際の初期化を行う追加の関数があります。

このアプローチの背後にある理由を理解したことがありません。私は、オブジェクトが完全に初期化され、構築後に使用できる 1 段階の構築をサポートするグループにしっかりと属しています。

1 段階のコンストラクターは、オブジェクトを完全に初期化できなかった場合にスローする必要があります。オブジェクトを初期化できない場合、その存在を許可してはならないため、コンストラクターはスローする必要があります。

于 2008-09-16T22:03:59.713 に答える
71

Eric Lippertは、4種類の例外があると言います。

  • 致命的な例外はあなたのせいではなく、あなたはそれらを防ぐことはできず、あなたはそれらから賢明に片付けることはできません。
  • 骨頭の例外はあなた自身のひどい欠点です、あなたはそれらを防ぐことができたかもしれません、そしてそれ故にそれらはあなたのコードのバグです。
  • 厄介な例外は、不幸な設計上の決定の結果です。厄介な例外は、完全に例外ではない状況でスローされるため、常にキャッチして処理する必要があります。
  • そして最後に、外因性の例外は、不幸な設計上の選択の結果ではないことを除けば、厄介な例外のように見えます。むしろ、それらは、美しく鮮明なプログラムロジックに影響を与える乱雑な外部の現実の結果です。

コンストラクターがそれ自体で致命的な例外をスローすることはありませんが、コンストラクターが実行するコードは致命的な例外を引き起こす可能性があります。「メモリ不足」のようなものは制御できるものではありませんが、コンストラクターで発生した場合は、発生します。

骨頭の例外はどのコードでも発生しないはずなので、すぐに発生します。

厄介な例外(例はInt32.Parse())は、例外的でない状況がないため、コンストラクターによってスローされるべきではありません。

最後に、外因性の例外は回避する必要がありますが、コンストラクターで外部の状況(ネットワークやファイルシステムなど)に依存する何かを実行している場合は、例外をスローするのが適切です。

参照リンク:https ://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/

于 2008-09-16T22:11:36.513 に答える
36

一般に、オブジェクトの初期化を構築から切り離しても、得られるものは何もありません。RAII は正しいです。コンストラクターの呼び出しが成功すると、完全に初期化されたライブ オブジェクトが生成されるか、失敗する必要があります。コード パスの任意の時点ですべてのエラーが発生すると、常に例外がスローされます。別の init() メソッドを使用しても、あるレベルで複雑さが増すことを除けば、何も得られません。ctor コントラクトは、機能する有効なオブジェクトを返すか、それ自体をクリーンアップしてスローする必要があります。

別の init メソッドを実装する場合でも、それを呼び出す必要があることを考慮してください。それでも例外をスローする可能性はありますが、それらはまだ処理する必要があり、事実上常にコンストラクターの直後に呼び出す必要があります.失敗した vs 有効で存在しない)。

いずれにせよ、私は 25 年間の OO 開発のケースで、別の init メソッドが「問題を解決する」ように思われるケースに出くわしましたが、これは設計上の欠陥です。オブジェクトが今必要ない場合は、今それを作成するべきではありません。今必要な場合は、初期化する必要があります。KISS は常に従うべき原則であり、任意のインターフェイスの動作、状態、および API は、オブジェクトがどのように行うかではなく、オブジェクトが何を行うかを反映する必要があるという単純な概念とともに、クライアント コードは、オブジェクトに何らかの種類があることさえ認識してはなりません。したがって、初期化を必要とする内部状態の初期化パターンはこの原則に違反します。

于 2008-09-17T12:07:01.497 に答える
7

部分的に作成されたクラスが引き起こす可能性のあるさまざまな問題のため、私は決してそうは言いません。

構築中に何かを検証する必要がある場合は、コンストラクターをプライベートにして、パブリックの静的ファクトリ メソッドを定義します。何かが無効な場合、メソッドはスローできます。しかし、すべてがチェックアウトされると、スローしないことが保証されているコンストラクターが呼び出されます。

于 2008-09-16T22:01:49.563 に答える
6

コンストラクターは、オブジェクトの構築を完了できない場合に例外をスローする必要があります。

たとえば、コンストラクターが 1024 KB の RAM を割り当てる必要があり、割り当てに失敗した場合、例外をスローする必要があります。これにより、コンストラクターの呼び出し元は、オブジェクトを使用する準備ができておらず、エラーが発生したことを認識します。どこかを修正する必要があります。

半分初期化され半分死んでいるオブジェクトは、呼び出し元が実際に知る方法がないため、問題や問題を引き起こすだけです。true または false を返す isOK() 関数の呼び出しを実行するプログラミングに頼るよりも、問題が発生したときにコンストラクターにエラーをスローさせたいと思います。

于 2008-09-16T22:30:04.520 に答える
5

コンストラクターが適切にクリーンアップする限り、コンストラクターが例外をスローすることは合理的です。RAIIパラダイム (Resource Acquisition Is Initialization) に従う場合、コンストラクターが意味のある作業を行うこと非常に一般的です。適切に作成されたコンストラクターは、完全に初期化できない場合、それ自体をクリーンアップします。

于 2008-09-16T22:06:07.197 に答える
5

特にコンストラクター内でリソースを割り当てている場合は特に、かなり危険です。言語によってはデストラクタが呼び出されないため、手動でクリーンアップする必要があります。オブジェクトの存続期間が言語でどのように始まるかによって異なります。

私が実際にそれを行ったのは、どこかにセキュリティ上の問題があり、オブジェクトを作成できないどころか作成できないことを意味する場合だけです。

于 2008-09-16T22:03:29.700 に答える
3

UIコントロール(ASPX、WinForms、WPFなど)を作成している場合は、デザイナー(Visual Studio)がコントロールを作成するときに例外を処理できないため、コンストラクターで例外をスローしないようにする必要があります。コントロールのライフサイクル(コントロールイベント)を把握し、可能な限り遅延初期化を使用します。

于 2008-12-23T17:28:59.623 に答える
3

[[[MyObj alloc] init] autorelease]イニシャライザで例外をスローすると、例外が自動解放をスキップするため、パターンを使用しているコードがあるとリークが発生することに注意してください。

この質問を参照してください:

initで例外を発生させるときにリークを防ぐにはどうすればよいですか?

于 2011-02-15T16:13:25.220 に答える
3

C++ FAQ セクション17.2および17.4を参照してください。

一般に、コンストラクターが失敗しないように記述されている場合、コードの移植と結果の維持が容易であり、失敗する可能性のあるコードは、エラー コードを返し、オブジェクトを不活性状態のままにする別のメソッドに配置されることがわかりました。 .

于 2008-09-16T22:04:55.623 に答える
2

コンストラクターでオブジェクトを初期化できない場合は、例外をスローします。1 つの例は、不正な引数です。

一般的な経験則として、例外は常にできるだけ早くスローする必要があります。これは、問題の原因が何かが間違っていることを示すメソッドに近い場合にデバッグが容易になるためです。

于 2008-09-16T22:38:11.113 に答える
2

有効なオブジェクトを作成できない場合は、コンストラクターから例外をスローする必要があります。これにより、クラスに適切な不変条件を提供できます。

実際には、非常に注意する必要があるかもしれません。C++ ではデストラクタが呼び出されないことに注意してください。したがって、リソースを割り当てた後にスローする場合は、適切に処理するよう細心の注意を払う必要があります。

このページでは、C++ の状況について詳しく説明しています。

于 2008-09-16T22:08:23.323 に答える
1

OOの通常の契約は、オブジェクトメソッドが実際に機能することです。

したがって、裏付けとして、コンストラクタ/initからゾンビオブジェクトを返さないようにします。

ゾンビは機能しておらず、内部コンポーネントが欠落している可能性があります。発生するのを待っている単なるnullポインタ例外。

私は何年も前に、ObjectiveCで最初にゾンビを作りました。

すべての経験則と同様に、「例外」があります。

特定のインターフェースが、例外をスローすることを許可されているメソッド「初期化」が存在することを示すコントラクトを持っている可能性は十分にあります。このインターフェイスを実装するオブジェクトは、initializeが呼び出されるまで、プロパティセッター以外の呼び出しに正しく応答しない場合があります。起動プロセス中にOOオペレーティングシステムのデバイスドライバーにこれを使用しましたが、実行可能でした。

一般に、ゾンビオブジェクトは必要ありません。Smalltalkのような言語では、 becomeを使用すると少し混乱しますが、becomeを使いすぎるのも悪いスタイルです。「Become」を使用すると、オブジェクトをその場で別のオブジェクトに変更できるため、エンベロープラッパー(高度なC ++)やストラテジーパターン(GOF)は必要ありません。

于 2008-09-16T22:10:24.317 に答える
1

Objective-Cでベストプラクティスに取り組むことはできませんが、C ++では、コンストラクターが例外をスローすることは問題ありません。特に、isOK()メソッドを呼び出さずに、構築時に発生した例外的な状態が報告されるようにする方法は他にありません。

関数tryブロック機能は、コンストラクターのメンバーごとの初期化の失敗をサポートするように特別に設計されています(ただし、通常の関数にも使用できます)。これは、スローされる例外情報を変更または強化する唯一の方法です。ただし、元の設計目的(コンストラクターでの使用)のため、空のcatch()句に例外を飲み込むことはできません。

于 2008-09-16T22:18:01.553 に答える
1

構築中に例外をスローすることは、コードをより複雑にする優れた方法です。簡単そうに見えたことが、急に難しくなる。たとえば、スタックがあるとします。スタックをポップしてトップ値を返すにはどうすればよいですか? スタック内のオブジェクトがコンストラクターをスローできる場合 (一時的なものを構築して呼び出し元に返す)、データを失わないことを保証することはできません (スタック ポインターをデクリメントし、値のコピー コンストラクターを使用して戻り値を構築します)。スタック、スローされ、アイテムを失ったばかりのスタックがあります)! これが、std::stack::pop が値を返さない理由であり、std::stack::top を呼び出す必要があります。

この問題については、ここで詳しく説明されています。項目 10、例外セーフ コードの記述を確認してください。

于 2008-09-16T22:08:10.967 に答える
1

はい、コンストラクターがその内部部分の1つを構築できなかった場合、コンストラクターのドキュメントに正式に記載されている明示的な例外をスローする(および特定の言語で宣言する)責任があります。

これは唯一のオプションではありません: コンストラクターを終了してオブジェクトを構築することができますが、一貫性のない状態を通知できるようにするために、'isCoherent()' メソッドが false を返します (特定のケースでは、例外による実行ワークフローの残忍な中断を回避するため)
警告: EricSchaefer のコメントで述べられているように、単体テストが複雑になる可能性があります (スローは、トリガーされる条件により、関数の循環的な複雑さを増加させる可能性があります)。それ)

呼び出し元が原因で失敗した場合 (呼び出し元によって提供された null 引数のように、呼び出されたコンストラクターが null 以外の引数を期待している場合)、コンストラクターは未チェックのランタイム例外をスローします。

于 2008-09-16T22:06:25.120 に答える
1

答えが完全に言語に依存しないかどうかはわかりません。一部の言語では、例外とメモリ管理が異なる方法で処理されます。

私は以前、例外を絶対に使用せず、イニシャライザでエラー コードのみを要求するコーディング標準の下で働いていました。ガベージ コレクションを使用しない言語では、ヒープとスタックの処理が大きく異なります。これは、非 RAII オブジェクトにとって問題になる場合があります。ただし、コンストラクターの後にイニシャライザーを呼び出す必要があるかどうかをデフォルトで認識できるように、チームが一貫性を保つことを決定することが重要です。すべてのメソッド (コンストラクターを含む) は、スローできる例外について十分に文書化されている必要があります。これにより、呼び出し元はそれらの処理方法を知ることができます。

オブジェクトの初期化を忘れがちなので、私は通常、単一段階の構築を支持しますが、それには多くの例外があります。

  • 例外に対するあなたの言語サポートはあまり良くありません。
  • まだ使用しなければならない差し迫った設計上の理由がnewあり、delete
  • 初期化はプロセッサを集中的に使用するため、オブジェクトを作成したスレッドに対して非同期で実行する必要があります。
  • 別の言語を使用するアプリケーションへのインターフェイスの外部で例外をスローする可能性のある DLL を作成しています。この場合、例外をスローしないという問題ではなく、パブリック インターフェイスの前にキャッチされるようにすることです。(C# で C++ 例外をキャッチすることはできますが、ジャンプするためのフープがあります。)
  • 静的コンストラクター (C#)
于 2018-02-28T15:38:06.580 に答える
1

OPの質問には「言語に依存しない」タグが付いています...この質問は、すべての言語/状況で同じ方法で安全に回答することはできません.

次の C# の例のクラス階層は、クラス B のコンストラクターをスローしIDisposeable.Dispose、メインの の終了時にクラス A の即時呼び出しをスキップしusing、クラス A のリソースの明示的な破棄をスキップします。

たとえば、クラス A がSocket構築時にネットワーク リソースに接続された を作成した場合、ブロック後も同様である可能性がありusingます (比較的隠れた異常)。

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("Initialize A's resources.");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose A's resources.");
    }
}

class B : A, IDisposable
{
    public B()
    {
        Console.WriteLine("Initialize B's resources.");
        throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose B's resources.");
        base.Dispose();
    }
}
class C : B, IDisposable
{
    public C()
    {
        Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose C's resources.");
        base.Dispose();
    }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (C c = new C())
            {
            }
        }
        catch
        {           
        }

        // Resource's allocated by c's "A" not explicitly disposed.
    }
}
于 2018-07-11T04:31:51.370 に答える
0

Javaの観点から厳密に言えば、コンストラクターを不正な値で初期化するときはいつでも、例外をスローする必要があります。そうすれば、悪い状態で構築されることはありません。

于 2008-09-16T22:10:16.937 に答える
0

私にとって、それはやや哲学的な設計上の決定です。

ctor以降、存在する限り有効なインスタンスがあると非常に便利です。多くの重要なケースでは、メモリ/リソースの割り当てを行うことができない場合、これにはctorからの例外のスローが必要になる場合があります。

他のいくつかのアプローチは、独自の問題を伴うinit()メソッドです。その1つは、init()が実際に呼び出されるようにすることです。

バリアントは、怠惰なアプローチを使用して、アクセサー/ミューテーターが最初に呼び出されたときにinit()を自動的に呼び出しますが、これには、潜在的な呼び出し元がオブジェクトが有効であることを心配する必要があります。(「それは存在するので、それは有効な哲学です」とは対照的です)。

この問題に対処するために提案されたさまざまなデザインパターンも見てきました。たとえば、ctorを介して初期オブジェクトを作成できますが、init()を呼び出して、アクセサー/ミューテーターを含む、含まれている初期化されたオブジェクトを取得する必要があります。

それぞれのアプローチには浮き沈みがあります。私はこれらすべてをうまく使いました。作成した瞬間からすぐに使用できるオブジェクトを作成しない場合は、ユーザーがinit()の前に対話しないように、大量のアサートまたは例外を使用することをお勧めします。

補遺

私はC++プログラマーの観点から書きました。また、例外がスローされたときに解放されるリソースを処理するために、RAIIイディオムを適切に使用していることを前提としています。

于 2008-09-16T22:10:22.867 に答える
0

すべてのオブジェクト作成にファクトリまたはファクトリ メソッドを使用すると、コンストラクタから例外をスローすることなく、無効なオブジェクトを回避できます。作成メソッドは、作成できる場合は要求されたオブジェクトを返し、作成できない場合は null を返す必要があります。null を返しても、オブジェクトの作成で何が問題だったのかがわからないため、クラスのユーザーで構築エラーを処理する際の柔軟性が少し失われます。ただし、オブジェクトを要求するたびに複数の例外ハンドラーの複雑さを追加することや、処理すべきではない例外をキャッチするリスクも回避できます。

于 2008-09-17T17:11:05.150 に答える
0

例外について私が見た最善のアドバイスは、事後条件を満たさないか、不変条件を維持できない場合に限り、例外をスローすることです。

そのアドバイスは、不明確な主観的な決定 (それは良いアイデアか) を、既に行っているはずの設計上の決定 (不変条件と事後条件) に基づく技術的で正確な質問に置き換えます。

コンストラクターは、そのアドバイスの特別なケースではありませんが、特定のケースです。そこで問題は、クラスにどのような不変条件が必要かということです。構築後に呼び出される個別の初期化メソッドの支持者は、クラスには 2 つ以上の動作モードがあり、構築後に準備モードと、初期化後に入る少なくとも 1 つの準備完了モードがあることを示唆しています。これはさらに複雑ですが、クラスに複数の動作モードがある場合は許容されます。クラスに動作モードがない場合、その複雑さがどのように価値があるかを理解するのは困難です。

セットアップを別の初期化メソッドにプッシュしても、例外のスローを回避できないことに注意してください。コンストラクターがスローした可能性のある例外は、初期化メソッドによってスローされるようになりました。クラスのすべての便利なメソッドは、初期化されていないオブジェクトに対して呼び出された場合、例外をスローする必要があります。

また、コンストラクターによって例外がスローされる可能性を回避することは面倒であり、多くの標準ライブラリでは多くの場合不可能であることに注意してください。これは、これらのライブラリの設計者が、コンストラクターから例外をスローすることをお勧めすると考えているためです。特に、共有不可能または有限のリソースを取得しようとする操作 (メモリの割り当てなど) は失敗する可能性があり、その失敗は通常、例外をスローすることによって OO 言語とライブラリで示されます。

于 2017-10-12T08:41:25.423 に答える