回答:
以下のために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
はのデフォルトコンストラクターを呼び出してから3にA
初期化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データメンバーにデフォルトのコンストラクターがあり、コンストラクター初期化子リストを使用しない場合、それらをデフォルトの状態に初期化するため、意図した状態に初期化できません。
参照は単に宣言して後で初期化することはできないため、コンパイラーがコンストラクターに入るときに参照データメンバーを初期化する必要があります。これは、コンストラクタ初期化子リストでのみ可能です。
パフォーマンスの問題の隣に、コードの保守性と拡張性と呼ぶ非常に重要な問題があります。
TがPODであり、初期化リストを優先する場合、一度Tが非PODタイプに変更される場合、すでに最適化されているため、不必要なコンストラクター呼び出しを回避するために初期化の周りを変更する必要はありません。
タイプTにデフォルトコンストラクターと1つ以上のユーザー定義コンストラクターがあり、デフォルトコンストラクターを削除または非表示にすることを一度決定した場合、初期化リストを使用した場合、ユーザー定義コンストラクターの場合、コードを更新する必要はありません。それらはすでに正しく実装されています。
constメンバーまたは参照メンバーと同じように、最初にTが次のように定義されているとします。
struct T
{
T() { a = 5; }
private:
int a;
};
次に、aをconstとして修飾することにしました。最初から初期化リストを使用する場合、これは1行の変更でしたが、上記のようにTが定義されているため、割り当てを削除するためにコンストラクター定義を掘る必要があります。
struct T
{
T() : a(5) {} // 2. that requires changes here too
private:
const int a; // 1. one line change
};
「コードモンキー」ではなく、自分が何をしているかについてより深い考察に基づいて決定を下すエンジニアによってコードが記述されていれば、保守がはるかに簡単になり、エラーが発生しにくくなります。
// 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;
}
};
ここで、コンパイラは次の手順に従ってタイプMyClass
1のオブジェクトを作成します。タイプのコンストラクタは、「a」に対して最初に呼び出されます。
2.「Type」の代入演算子がMyClass()コンストラクターの本体内で呼び出され、代入する
variable = a;
そして、最後に「タイプ」のデストラクタが範囲外になるため、「a」が呼び出されます。
次に、Initializer Listを使用した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
}
};
イニシャライザリストでは、次の手順の後にコンパイラが続きます。
メンバー初期化リストがどの程度の違いを生み出すことができるかを示すために、いくつかの追加情報を追加するだけです。leetcode 303の範囲合計クエリ-不変、https: //leetcode.com/problems/range-sum-query-immutable/特定のサイズのベクトルを作成してゼロに初期化する必要があります。2つの異なる実装と速度の比較を次に示します。
メンバー初期化リストがないと、ACを取得するのに約212ミリ秒かかりました。
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
preSum = vector<int>(nums.size()+1, 0);
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
現在、メンバー初期化リストを使用しているため、ACを取得する時間は約108ミリ秒です。この単純な例では、メンバー初期化リストの方がはるかに効率的であることは明らかです。すべての測定はLCからの実行時間からです。
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) {
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
構文:
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}とクラスメンバーvariable(i)がオーバーライドされました。回避するには、どちらかを使用する必要があります
1. Initialization list
2. this operator.
C ++コアガイドラインC.49で説明されて いるように、コンストラクターでの割り当てよりも初期化を優先すると、デフォルトコンストラクターへの不要な呼び出しが防止されます。