私はコンストラクターでメンバー初期化リストを使用することに部分的です...しかし、この背後にある理由をずっと忘れていました...
コンストラクターでメンバー初期化リストを使用しますか? もしそうなら、なぜですか?そうでない場合、なぜですか?
私はコンストラクターでメンバー初期化リストを使用することに部分的です...しかし、この背後にある理由をずっと忘れていました...
コンストラクターでメンバー初期化リストを使用しますか? もしそうなら、なぜですか?そうでない場合、なぜですか?
PODクラスのメンバーの場合、違いはありません。単にスタイルの問題です。クラスであるクラスメンバーの場合、デフォルトのコンストラクターへの不要な呼び出しを回避します。検討:
class A
{
public:
A() { x = 0; }
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B()
{
a.x = 3;
}
private:
A a;
};
この場合、 のコンストラクターB
は のデフォルトのコンストラクターを呼び出し、 3A
に初期化a.x
します。より良い方法は、のコンストラクターが初期化子リストで のコンストラクB
ターを直接呼び出すことです。A
B()
: a(3)
{
}
これは のコンストラクタのみを呼び出しA
、A(int)
デフォルトのコンストラクタは呼び出しません。この例では、違いはごくわずかですが、A
メモリの割り当てやファイルのオープンなど、デフォルトのコンストラクターがさらに多くのことを行ったと想像してみてください。不必要にそれをしたくないでしょう。
さらに、クラスにデフォルトのコンストラクターがない場合、またはconst
メンバー変数がある場合は、初期化リストを使用する必要があります。
class A
{
public:
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B() : a(3), y(2) // 'a' and 'y' MUST be initialized in an initializer list;
{ // it is an error not to do so
}
private:
A a;
const int y;
};
上記のパフォーマンス上の理由とは別に、クラスがコンストラクターパラメーターとして渡されたオブジェクトへの参照を格納する場合、またはクラスに const 変数がある場合は、初期化リストを使用する以外に選択肢がありません。
ここでの回答に記載されていないコンストラクター初期化子リストを使用する重要な理由の 1 つは、基本クラスの初期化です。
構築の順序に従って、基本クラスは子クラスの前に構築する必要があります。コンストラクター初期化子リストがない場合、基本クラスにデフォルトのコンストラクターがあり、子クラスのコンストラクターに入る直前に呼び出される場合、これが可能です。
ただし、基本クラスにパラメーター化されたコンストラクターしかない場合は、コンストラクター初期化子リストを使用して、基本クラスが子クラスの前に初期化されるようにする必要があります。
パラメータ化されたコンストラクタのみを持つサブオブジェクトの初期化
効率
コンストラクター初期化子リストを使用して、最初にデータ メンバーを既定の状態に初期化してからコードで必要な状態に変更するのではなく、データ メンバーをコードで必要な正確な状態に初期化します。
クラス内の非静的 const データ メンバーにデフォルト コンストラクターがあり、コンストラクター初期化子リストを使用しない場合、それらはデフォルト状態に初期化されるため、意図した状態に初期化することはできません。
参照を宣言して後で初期化することはできないため、コンパイラがコンストラクターに入るときに参照データ メンバーを初期化する必要があります。これは、コンストラクター初期化子リストでのみ可能です。
パフォーマンスの問題の次に、コードの保守性と拡張性と呼ぶ非常に重要な問題がもう 1 つあります。
aT
が POD であり、初期化リストを優先するようになった場合、POD 以外の型に一度T
変更された場合は、既に最適化されているため、不必要なコンストラクターの呼び出しを避けるために初期化に関して何も変更する必要はありません。
型T
にデフォルト コンストラクターと 1 つ以上のユーザー定義コンストラクターがあり、一度デフォルト コンストラクターを削除または非表示にすることにした場合、初期化リストが使用されていれば、ユーザー定義コンストラクターのコードを更新する必要はありません。すでに正しく実装されています。
メンバーまたは参照メンバーと同じで、最初は次のように定義されているconst
としましょう:T
struct T
{
T() { a = 5; }
private:
int a;
};
次に、最初から初期化リストを使用する場合、これは 1 行の変更でしたが、a
上記のように定義されているため、代入を削除するためにコンストラクター定義を掘り下げる必要もあります。const
T
struct T
{
T() : a(5) {} // 2. that requires changes here too
private:
const int a; // 1. one line change
};
コードが「コード モンキー」によってではなく、自分が何をしているかについてより深い考察に基づいて決定を下すエンジニアによって書かれた場合、メンテナンスがはるかに簡単になり、エラーが発生しにくくなることは周知の事実です。
コンストラクターの本体が実行される前に、その親クラスのすべてのコンストラクターとそのフィールドのコンストラクターが呼び出されます。デフォルトでは、引数のないコンストラクターが呼び出されます。初期化リストを使用すると、呼び出されるコンストラクターとコンストラクターが受け取る引数を選択できます。
参照または const フィールドがある場合、または使用するクラスの 1 つにデフォルトのコンストラクターがない場合は、初期化リストを使用する必要があります。
// Without Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
variable = a;
}
};
ここで、コンパイラは次の手順に従って type のオブジェクトを作成しますMyClass
。
Type
のコンストラクタは、「<code>a」に対して最初に呼び出されます。MyClass()
コンストラクタ本体内で呼び出して代入します。variable = a;
MyClass()
ここで、初期化リストを持つコンストラクターを使用した同じコードを考えてみましょう。
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
}
};
Initializer List を使用すると、コンパイラは次の手順に従います。
"<code>Type" クラスのコピー コンストラクターが呼び出され、初期化されますvariable(a)
。初期化子リストの引数は、構造体「<code>変数」を直接コピーするために使用されます。
「<code>a」はスコープ外なので、「<code>Type」のデストラクタを呼び出します。
構文:
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample(): Sam_x(1), Sam_y(2) /* Classname: Initialization List */
{
// Constructor body
}
};
初期化リストの必要性:
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample() */* Object and variables are created - i.e.:declaration of variables */*
{ // Constructor body starts
Sam_x = 1; */* Defining a value to the variable */*
Sam_y = 2;
} // Constructor body ends
};
上記のプログラムでは、クラスのコンストラクターが実行されると、Sam_xとSam_yが作成されます。次に、コンストラクター本体で、それらのメンバー データ変数が定義されます。
使用例:
C では、作成時に変数を定義する必要があります。C++ と同様に、オブジェクトの作成中に初期化リストを使用して Const および Reference 変数を初期化する必要があります。オブジェクトの作成後に初期化を行うと (コンストラクタ本体内)、コンパイル時エラーが発生します。
デフォルトのコンストラクターを持たない Sample1 (基本) クラスのメンバー オブジェクト
class Sample1
{
int i;
public:
Sample1 (int temp)
{
i = temp;
}
};
// Class Sample2 contains object of Sample1
class Sample2
{
Sample1 a;
public:
Sample2 (int x): a(x) /* Initializer list must be used */
{
}
};
派生クラスのコンストラクターを内部的に呼び出し、基本クラスのコンストラクターを呼び出す派生クラスのオブジェクトを作成している間 (デフォルト)。基本クラスにデフォルトのコンストラクターがない場合、ユーザーはコンパイル時エラーを受け取ります。回避するには、次のいずれかが必要です
1. Default constructor of Sample1 class
2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
クラス コンストラクターのパラメーター名とクラスのデータ メンバーは同じです。
class Sample3 {
int i; /* Member variable name : i */
public:
Sample3 (int i) /* Local variable name : i */
{
i = i;
print(i); /* Local variable: Prints the correct value which we passed in constructor */
}
int getI() const
{
print(i); /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
return i;
}
};
ご存知のように、両方の変数が同じ名前を持つ場合、ローカル変数が最も優先度が高く、次にグローバル変数が優先されます。この場合、プログラムは "i" 値 {左側と右側の両方の変数。つまり、Sample3() コンストラクターのローカル変数としての i = i} と、クラス メンバー変数(i) がオーバーライドされました。回避するには、どちらかを使用する必要があります
1. Initialization list
2. this operator.