C++ でイニシャライザ リストを使用するのはどのような場合ですか?

Initializer List は、クラスのデータ メンバーを初期化する際に使用されます。初期化されるメンバーのリストは、コロンが後に続くコンマ区切りのリストとしてコンストラクターで指定されます。以下は、初期化リストを使用して Point クラスの x と y を初期化する例です。

C++




#include> using> namespace> std;> class> Point {> private> :> > int> x;> > int> y;> public> :> > Point(> int> i = 0,> int> j = 0): x(i), y(j) {}> > /* The above use of Initializer list is optional as the> > constructor can also be written as:> > Point(int i = 0, int j = 0) {> > x = i;> > y = j;> > }> > */> > int> getX()> const> {> return> x; }> > int> getY()> const> {> return> y; }> };> int> main()> {> > Point t1(10, 15);> > cout < <> 'x = '> < < t1.getX() < <> ', '> ;> > cout < <> 'y = '> < < t1.getY();> > return> 0;> }>

出力

x = 10, y = 15 

上記のコードは、Initializer リストの構文の単なる例です。上記のコードでは、x と y をコンストラクター内で簡単に初期化することもできます。ただし、コンストラクター内のデータ メンバーの初期化が機能せず、Initializer List を使用する必要がある状況があります。そのような場合は次のとおりです。

1. 非静的constデータメンバの初期化の場合

const データ メンバーは、Initializer List を使用して初期化する必要があります。次の例では、 t は Test クラスの const データ メンバーであり、Initializer List を使用して初期化されます。初期化子リストで const データ メンバーを初期化する理由は、const データ メンバーに個別にメモリが割り当てられず、シンボル テーブル内で折り畳まれるため、初期化子リストで初期化する必要があるためです。

また、これはパラメーター化されたコンストラクターであり、代入演算子を呼び出す必要がないため、余分な操作が 1 つ回避されます。

C++




// C++ progmram to demonstrate the use of> // initializer list to initialize the const> // data member> #include> using> namespace> std;> class> Test {> > const> int> t;> public> :> > //Initializer list must be used> > Test(> int> t):t(t) {}> > int> getT() {> return> t; }> };> int> main() {> > Test t1(10);> > cout < return 0; }>

出力

10 

2. 参照メンバの初期化について

参照メンバーは、Initializer List を使用して初期化する必要があります。次の例では、 t は Test クラスの参照メンバーであり、Initializer List を使用して初期化されます。

C++




// Initialization of reference data members> #include> using> namespace> std;> class> Test {> > int> &t;> public> :> > Test(> int> &t):t(t) {}> //Initializer list must be used> > int> getT() {> return> t; }> };> int> main() {> > int> x = 20;> > Test t1(x);> > cout < x = 30; cout < return 0; }>

出力

20 30 

3. デフォルトコンストラクターを持たないメンバーオブジェクトの初期化の場合

次の例では、クラス A のオブジェクト a はクラス B のデータ メンバーであり、A にはデフォルトのコンストラクターがありません。初期化には初期化子リストを使用する必要があります。

C++




// C++ progmam to initialize a member object without default> // constructor> #include> using> namespace> std;> class> A {> > int> i;> public> :> > A(> int> );> };> A::A(> int> arg)> {> > i = arg;> > cout < <> 'A's Constructor called: Value of i: '> < < i> > < < endl;> }> // Class B contains object of A> class> B {> > A a;> public> :> > B(> int> );> };> B::B(> int> x) : a(x)> {> // Initializer list must be used> > cout < <> 'B's Constructor called'> ;> }> int> main()> {> > B obj(10);> > return> 0;> }>

出力

A's Constructor called: Value of i: 10 B's Constructor called 

クラス A にデフォルト コンストラクターとパラメーター化コンストラクターの両方がある場合、デフォルト コンストラクターを使用して を初期化する場合、Initializer List は必須ではありませんが、パラメーター化コンストラクターを使用して を初期化する場合は必須です。

4. 基本クラスのメンバーの初期化の場合

ポイント 3 と同様、基本クラスのパラメーター化されたコンストラクターは、Initializer List を使用してのみ呼び出すことができます。

C++




#include> using> namespace> std;> class> A {> > int> i;> public> :> > A(> int> );> };> A::A(> int> arg) {> > i = arg;> > cout < <> 'A's Constructor called: Value of i: '> < < i < < endl;> }> // Class B is derived from A> class> B: A {> public> :> > B(> int> );> };> B::B(> int> x):A(x) {> //Initializer list must be used> > cout < <> 'B's Constructor called'> ;> }> int> main() {> > B obj(10);> > return> 0;> }>

出力

A's Constructor called: Value of i: 10 B's Constructor called 

5. コンストラクタのパラメータ名がデータメンバと同じ場合

コンストラクターのパラメーター名がデータ メンバー名と同じ場合、データ メンバーは次のいずれかを使用して初期化する必要があります。 このポインタ またはイニシャライザリスト。次の例では、A() のメンバー名とパラメーター名は両方とも i です。

C++




#include> using> namespace> std;> class> A {> > int> i;> public> :> > A(> int> );> > int> getI()> const> {> return> i; }> };> A::A(> int> i) : i(i)> {> }> // Either Initializer list or this pointer must be used> /* The above constructor can also be written as> A::A(int i) {> > this->私 = 私;>> }> */> int> main()> {> > A a(10);> > cout < < a.getI();> > return> 0;> }>

出力

10 

6. パフォーマンス上の理由から

本体内で値を割り当てるのではなく、Initializer List ですべてのクラス変数を初期化することをお勧めします。次の例を考えてみましょう。

C++




// 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. デフォルトの構成変数

3. Type の代入演算子は、MyClass() コンストラクターの本体内で呼び出され、代入されます。

variable = a; 

4. そして最後に、スコープ外に出るため、 Type のデストラクターが呼び出されます。

ここで、Initializer List を含む MyClass() コンストラクターを使用した同じコードを考えてみましょう。

C++




// 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 を使用すると、コンパイラは次の手順に従います。

1. 型のコンストラクターが最初に a に対して呼び出されます。
2. Type クラスのパラメーター化されたコンストラクターが呼び出され、初期化されます: variable(a)。初期化子リストの引数は、構成変数を直接コピーするために使用されます。
3. Type のデストラクターは、スコープ外になるため、 a に対して呼び出されます。

この例からわかるように、コンストラクター本体内で代入を使用すると、コンストラクター + デストラクター + 1 つの追加代入演算子の呼び出しという 3 つの関数呼び出しが行われます。また、Initializer List を使用する場合、関数呼び出しはコピー コンストラクター + デストラクター呼び出しの 2 つだけです。この点に関する実行例については、この投稿を参照してください。

このような変数が多数存在する実際のアプリケーションでは、この代入ペナルティはさらに大きくなります。おかげで ptr この点を追加するために。

C++ におけるパラメータと均一な初期化

変換の縮小や予期しない動作の問題を回避するには、パラメーターの初期化 () ではなく、均一な初期化 {} を含む初期化リストを使用することをお勧めします。初期化中により厳密な型チェックを提供し、潜在的な縮小変換を防止します。

パラメータの初期化を使用したコード ()

C++




#include> class> Base {> > char> x;> public> :> > Base(> char> a)> > : x{ a }> > {> > }> > void> print() { std::cout < <> static_cast> <> int> >(バツ); }>> };> int> main()> {> > Base b{ 300 };> // Using uniform initialization with {}> > b.print();> > return> 0;> }>

出力

44 

上記のコードでは、値 300 は char の有効範囲外であるため、未定義の動作が発生し、誤った結果が生じる可能性があります。コンパイラは、コンパイル設定に応じて、この状況に対して警告またはエラーを生成する場合があります。

均一初期化を使用したコード {}

{} による均一な初期化を使用し、指定された値 a で x を初期化することにより、コンパイラーはより厳密な型チェックを実行し、コンパイル中に警告またはエラーを発行して、int から char への縮小変換を示します。
これは均一な初期化 {} を使用したコードです。これにより警告が発生するため、使用することをお勧めします。

C++




#include> class> Base {> > char> x;> public> :> > Base(> char> a)> > : x{ a }> > {> > }> > void> print() { std::cout < <> static_cast> <> int> >(バツ); }>> };> int> main()> {> > Base b{ 300 };> // Using uniform initialization with {}> > b.print();> > return> 0;> }>

main.cpp: In function ‘int main()’: main.cpp:17:17: error: narrowing conversion of ‘300’ from ‘int’ to ‘char’ [-Wnarrowing] 17 | Base b{ 300 }; // Using uniform initialization with {} | ^