新しい会社に入社したばかりで、コードベースの多くはコンストラクターではなく初期化メソッドを使用しています。
struct MyFancyClass : theUberClass
{
MyFancyClass();
~MyFancyClass();
resultType initMyFancyClass(fancyArgument arg1, classyArgument arg2,
redundantArgument arg3=TODO);
// several fancy methods...
};
これはタイミングと関係があると私に言った。いくつかのことを行う必要があることafterコンストラクタで失敗する構築。しかし、ほとんどのコンストラクターは空であり、コンストラクターを使用しない理由は実際にはありません。
それでは、C++の魔法使いよ。あなたがコンストラクターではなくinit-methodを使用するのはなぜですか。
彼らは「タイミング」と言うので、彼らはinit関数がオブジェクト上の仮想関数を呼び出せるようにしたいからだと思います。基本クラスのコンストラクターでは、オブジェクトの派生クラス部分が「まだ存在しない」ため、特に派生クラスで定義された仮想関数にアクセスできないため、これは常にコンストラクターで機能するとは限りません。代わりに、定義されている場合、関数の基本クラスバージョンが呼び出されます。定義されていない場合(関数が純粋仮想であることを意味します)、未定義の動作が発生します。
Init関数のもう1つの一般的な理由は、例外を避けたいという願望ですが、それはかなり昔ながらのプログラミングスタイルです(そして、それが良いアイデアであるかどうかは、それ自体の議論です)。コンストラクターで機能しないものとは関係ありません。何かが失敗した場合、コンストラクターがエラー値を返すことができないという事実とは関係ありません。ですから、あなたの同僚があなたに本当の理由を与えている限り、私はこれがそうではないと思う。
はい、いくつか考えられますが、一般的には良い考えではありません。
ほとんどの場合、呼び出される理由は、コンストラクターで例外を介してエラーを報告するだけです(これは正しい)が、従来のメソッドではエラーコードを返すことができます。
ただし、適切に設計されたオブジェクト指向コードでは、コンストラクターがクラスの不変式を確立します。デフォルトのコンストラクターを許可することにより、空のクラスを許可するため、「null」クラスと「意味のある」クラスの両方を受け入れるように不変式を変更する必要があります。クラスを使用するたびに、最初にオブジェクトが適切に構築されています...それはひどいです。
それでは、「理由」を明らかにしましょう。
virtual
メソッドを使用する必要があります。VirtualConstructorイディオムを使用します。assert
を忘れずに、使用する前に使用可能です。operator=
を使用します(コンパイラ生成バージョンがニーズに合わない場合は、コピーアンドスワップイディオムを使用して実装します)。前述のように、一般的に、悪い考えです。本当に「void」コンストラクターが必要な場合は、private
にし、Builderメソッドを使用します。 NRVOと同じくらい効率的です...そして、構築が失敗した場合にboost::optional<FancyObject>
を返すことができます。
他にも多くの考えられる理由が挙げられています(そしてこれらのほとんどが一般に良い考えではない理由の適切な説明)。 実際にはタイミングに関係するinitメソッドの(多かれ少なかれ)有効な使用の例を1つ投稿します。
以前のプロジェクトでは、それぞれが階層の一部であり、さまざまな方法で相互参照する多数のサービスクラスとオブジェクトがありました。そのため、通常、ServiceAを作成するには、初期化時に特定のサービス(場合によってはServiceA自体を含む)の存在に既に依存しているサービスコンテナーを必要とする親サービスオブジェクトが必要でした。その理由は、初期化中に、ほとんどのサービスが特定のイベントのリスナーとして他のサービスに登録された、および/または初期化が成功したことを他のサービスに通知したためです。通知の時点で他のサービスが存在していなかった場合、登録は行われなかったため、このサービスは後でアプリケーションの使用中に重要なメッセージを受信しませんでした。 循環依存関係の連鎖を断ち切るを実現するために、コンストラクターとは別に明示的な初期化メソッドを使用する必要があったため、効果的にグローバルサービスの初期化を2フェーズプロセスにするとなりました。
したがって、このイディオムは一般的に従うべきではありませんが、私見にはいくつかの有効な用途があります。ただし、可能な限りコンストラクターを使用して、その使用を最小限に抑えることが最善です。私たちの場合、これはレガシープロジェクトであり、そのアーキテクチャをまだ完全には理解していませんでした。少なくともinitメソッドの使用はサービスクラスに限定されていました。通常のクラスはコンストラクターによって初期化されていました。サービス初期化メソッドの必要性を排除するためにそのアーキテクチャをリファクタリングする方法があるかもしれないと信じていますが、少なくともそれを行う方法がわかりませんでしたプロジェクトの一部)。
私が頭の外から考えることができる2つの理由:
このような初期化のもう1つの用途は、オブジェクトプールです。基本的には、プールからオブジェクトを要求するだけです。プールには、空白のN個のオブジェクトがすでに作成されています。メンバーを設定するために好きなメソッドを呼び出すことができるのは、呼び出し元です。呼び出し元がオブジェクトを処理すると、プールに破壊するように指示します。利点は、オブジェクトが使用されるまでメモリが保存され、呼び出し元がオブジェクトを初期化する独自の適切なメンバーメソッドを使用できることです。オブジェクトは多くの目的を果たしている可能性がありますが、呼び出し側はすべてを必要とせず、オブジェクトのすべてのメンバーを初期化する必要もないかもしれません。
通常、データベース接続を考えます。プールには多数の接続オブジェクトを含めることができ、呼び出し元はユーザー名、パスワードなどを入力できます。
init()関数は、コンパイラが例外をサポートしていない場合、またはターゲットアプリケーションがヒープを使用できない場合に適しています(例外は通常、ヒープを使用して作成および破棄します)。
init()ルーチンは、構築の順序を定義する必要がある場合にも役立ちます。つまり、オブジェクトをグローバルに割り当てる場合、コンストラクターが呼び出される順序は定義されていません。例えば:
[file1.cpp]
some_class instance1; //global instance
[file2.cpp]
other_class must_construct_before_instance1; //global instance
標準では、-instance1のコンストラクターの前にmust_construct_before_instance1のコンストラクターが呼び出されるという保証はありません。ハードウェアに関連付けられている場合、初期化の順序が重要になる場合があります。
特別な場合の詳細:リスナーを作成する場合、リスナーをどこかに登録する必要がある場合があります(シングルトンやGUIなど)。コンストラクターでそれを行うと、コンストラクターが完了していない(完全に失敗する可能性もある)ため、まだ安全ではない自身へのポインター/参照がリークします。すべてのリスナーを収集し、事態が発生したときにイベントを送信し、イベントを送信するシングルトンを想定し、リスナーのリスト(そのうちの1つは私たちが話しているインスタンスです)をループして、それぞれにメッセージを送信します。しかし、このインスタンスはまだコンストラクターの途中なので、呼び出しはあらゆる種類の悪い方法で失敗する可能性があります。この場合、別の関数に登録することは理にかなっています。これは、コンストラクター自体からnot呼び出しを行うことは明らかです(これは目的を完全に無効にします) )が、構築が完了した後、親オブジェクトから。
しかし、それは特定のケースであり、一般的なケースではありません。
リソース管理を行うのに便利です。オブジェクトの存続期間が終了したときにリソースを自動的に割り当て解除するデストラクタを持つクラスがあるとします。これらのリソースクラスを保持するクラスもあり、この上位クラスのコンストラクターでそれらを開始するとします。代入演算子を使用してこの上位クラスを開始するとどうなりますか?内容がコピーされると、古い上位クラスがコンテキストから外れ、デストラクタがすべてのリソースクラスに対して呼び出されます。これらのリソースクラスに割り当て中にコピーされたポインターがある場合、これらのポインターはすべて不良ポインターになります。代わりに、上位クラスの個別のinit関数でリソースクラスを開始する場合、割り当て演算子はこれらのクラスを作成および削除する必要がないため、リソースクラスのデストラクタは呼び出されないように完全にバイパスします。これが「タイミング」の要件が意味するものだと思います。
また、コードサンプルを添付して回答#1にしたいです-
またmsdnが言うので:
仮想メソッドが呼び出されると、メソッドを実行する実際の型は実行時まで選択されません。コンストラクターが仮想メソッドを呼び出すとき、メソッドを呼び出すインスタンスのコンストラクターが実行されていない可能性があります。
例:次の例は、この規則に違反した場合の効果を示しています。テストアプリケーションは、DerivedTypeのインスタンスを作成します。これにより、ベースクラス(BadlyConstructedType)コンストラクターが実行されます。 BadlyConstructedTypeのコンストラクターは、仮想メソッドDoSomethingを誤って呼び出します。出力が示すように、DerivedType.DoSomething()が実行され、DerivedTypeのコンストラクターが実行される前に実行されます。
using System;
namespace UsageLibrary
{
public class BadlyConstructedType
{
protected string initialized = "No";
public BadlyConstructedType()
{
Console.WriteLine("Calling base ctor.");
// Violates rule: DoNotCallOverridableMethodsInConstructors.
DoSomething();
}
// This will be overridden in the derived type.
public virtual void DoSomething()
{
Console.WriteLine ("Base DoSomething");
}
}
public class DerivedType : BadlyConstructedType
{
public DerivedType ()
{
Console.WriteLine("Calling derived ctor.");
initialized = "Yes";
}
public override void DoSomething()
{
Console.WriteLine("Derived DoSomething is called - initialized ? {0}", initialized);
}
}
public class TestBadlyConstructedType
{
public static void Main()
{
DerivedType derivedInstance = new DerivedType();
}
}
}
出力:
ベースctorを呼び出します。
派生DoSomethingが呼び出される-初期化されますか?番号
派生したctorを呼び出します。