web-dev-qa-db-ja.com

OOP言語は、常にオブジェクトを返すコンストラクタを持つことで何が得られますか?

意図的な設計決定のように思われる場合、C++にはオブジェクトと参照のnull値はありません。 nullチェックを実行する必要がないため、オブジェクトと参照の使用が非常にエレガントになります。また、コンストラクタは常にオブジェクトを返す必要があることも意味します。

void main () {
    // o is guaranteed to be non-null by c++:
    Object o = new Object();         
}

// Allowing this hypothetical "failing" constructor would break the guarantee:
Object() { return void }

オブジェクトの作成が失敗することがあります。簡単な例はConnectionオブジェクトです。 C++ではこれが許可されていないため、プログラマーは代替手段を開発しました:初期化メソッド(さまざまな名前)、静的ビルダーメソッド、ポリモーフィズムを使用しない「ファクトリー」、およびnullオブジェクト。 例外を追加する理由の1つは、C++コンストラクターのこの制限を回避する方法であった可能性があります 、しかしどちらの場合も、トレードオフです-nullチェックを回避しますが、時々厄介な構造があります。


これが、C++がコンストラクターがnullを返すことを許可しない理由を説明しています。しかし、新しいOOP JavaやC#などの言語を考慮すると、不十分です。そこで、参照をnullにすることができますが、コンストラクターはC++のように機能し、したがって、nullチェックと代替の構築スキーム(ファクトリー、initメソッドなどを記述する必要があります)の両方を記述できるようになります。

2
Fadeway

失敗したコンストラクタを処理する標準的な方法は、例外をスローすることです。通常、コンストラクターは失敗せず、通常、メインのロジックを雑然とさせるよりも、別の場所でそのような失敗を処理する方が簡単です。

Connectionコンストラクターが失敗した場合、そのConnectionで発生するはずのことは何も起こらないため、残りの関数またはtry ... catchブロックを実行するのではなくスローするのが適切です。

存在するかどうかに関係なくオブジェクトが必要な場合は、std :: optional(またはC++コンパイラが古い場合はboost :: optional)を検討してください。

6
David Thornley

C++の場合

C++では、コンストラクタは成功することが期待されています。コンストラクタが失敗する唯一の方法は、例外を発生させることです。

Bjarne Stroustrupは、彼の著書「C++の設計と進化」で、これが例外の最も重要な側面の1つであることを確認しています(第16.5.1章:コンストラクターのエラー)。あなたの質問で正しく指摘されました。

ここで、コンストラクタが例外なしに失敗する可能性があることを少し想像してください...

ポインタの初期化_Object *o = new Object;_は、nullptrを使用しても、このセマンティクスで問題なく動作する可能性があります。実際、失敗が 割り当ての問題nothrow .. によって引き起こされた場合、このセマンティックを取得することが可能です。

しかし、どのように次のケースを処理できますか?

  1. ローカル(値)オブジェクトの構築:_Object o;_は、たとえばデフォルトのコンストラクターを呼び出します。構築が失敗する可能性がある場合、ユーザーまたはコンパイラは、oの以降のすべての使用でvoidオブジェクトをチェックする必要があります。ほとんどの場合、これはパフォーマンスのオーバーヘッドであり、例外的なケースによってのみ正当化されますが、これはC++の設計哲学ではありません。
  2. クラスメンバーの構築:たとえば_class X { public: Object o; };_。 oの構築が失敗する場合、この単純な例では、_class X_オブジェクトの構築も失敗すると言うことができます。しかし、クラスXにいくつかの異なるメンバーがあり、そのうちのいくつかは正常に構築され、一部は失敗したオブジェクトを持つ場合、どのように機能しますか?そして、部分的に失敗した構築の場合に、指摘されたオブジェクトが破棄されるかどうかをコンパイラーが判断できない他のポインターメンバーがある場合はどうなりますか? X
  3. 派生クラスの構築:基本クラスオブジェクトが正常に構築できたが、派生クラスオブジェクトが構築できなかった場合の対処方法:bbaseクラスオブジェクトを返す必要がありますか?それとも破壊すべきか?
  4. 同様の問題がある多重継承や仮想継承については触れません。

C++がコンストラクターの失敗を許可する場合、多くの未解決のセマンティック問題、またはほとんどの場合役に立たない多くのオーバーヘッドがあります。これは、現在の設計オプションを説明します。例外的な状況を例外的に処理します。

ところで、C++ではObject o = new Object();は無効です。

コード設計の問題?

通常の動作として失敗することが予想されるコンストラクタが必要だと感じた場合は、設計上の問題があります。

次に、特別なfail状態のオブジェクトを使用することを検討する必要があります。次に、構築は常に成功しますが、オブジェクトの状態が予期したものではない可能性があることを再度検討します。次に、オブジェクトの状態に問題がないかどうかをいつでも確認できます。

JavaおよびC#のケース

JavaとC#も、コンストラクターが成功するか、例外が発生することを期待しています。その理由は、C++について述べたものとよく似ています。オブジェクトが参照によって管理されているためケース1は該当せず、これらの言語はMIをサポートしていないためケース4は関連しませんが、部分的に作成されたオブジェクトの場合、ケース2と3は意味上の問題を引き起こします。

言語がコンストラクタの失敗を例外なく許可する場合、部分的に成功した構築を処理する方法を決定する必要があります(正しいメンバーオブジェクトを保持し、もう一方をnilにするか、構築されたメンバーを破棄してnilを返します)。しかし、選択されたセマンティクスは、すべての場合に正しいものではない可能性があります。例外処理により、この点で柔軟性が高まります。

設計のにおいの解決策として、C++について既に述べた特別なフェイル状態に加えて、たとえば、構築パラメーターが無効な場合に、nilを返すことができるファクトリーまたはビルダーを簡単に使用することもできます。

7
Christophe

C++にはポインターに加えてスタックオブジェクトがあるため、C++を使用した例は機能しません。

int main
{ 
  Object x;
  x = nullptr; // x is not a pointer, this makes no sense.
}

また、voidは値ではないため、voidを返したり割り当てたりすることはできません。スタック変数の存在自体が、voidを返すコンストラクターの概念を意味のないものにします。スタックオブジェクトを許可しない別の言語では、それを実行することを想像できます。

Class OPQ
{
  Constructor(bool b) { if (b == false) return null; else return this; }
}

そして、おそらく、ガベージコレクションは、コンストラクターの終了時にOPQをクリーンアップします。これは、呼び出し側がnullの結果を処理する必要があることを意味しますが、その一方で、このような構成は例外のないプログラミング言語で機能することを意味します。 (おそらくnullオブジェクトの操作が何も行われないものです。)

だから私は結論として、一般的にどちらの方法でも強制はありません。 C++自動変数はこの概念を排除しますが、参照ベースの言語は好きなように実行できます。おそらくC#とJavaは、言語メーカーが例外を気に入っており、ある程度C++を模倣したいと考えていたため、彼らが行ったように選択しました。

3
Alan Baljeu

Swiftはこれで全く問題ありません。クラスのインスタンスは参照オブジェクトであり、オプションのクラスを使用できるため、失敗しないイニシャライザまたは失敗可能なイニシャライザを持つことができます。失敗する可能性のあるイニシャライザの実装は、言語が初期化されたオブジェクトの半分を正しくクリーンアップする必要があるため、どういうわけかトリッキーですが、プログラマには見えません。

Swiftの良い点は、オプションのオブジェクトがnilである可能性があるという事実を単に無視できないことです。

1
gnasher729