メンバー初期化リストを使用した方がよいのはなぜですか?


228

私はコンストラクターでメンバー初期化リストを使用するのが苦手です...しかし、これの背後にある理由を忘れて以来ずっと...

コンストラクターでメンバー初期化リストを使用していますか?もしそうなら、なぜですか?そうでない場合、なぜそうではないのですか?


3
ここにリストされている理由... https://www.geeksforgeeks.org/when-do-we-use-initializer-list-in-c/
u8it

回答:


278

以下のために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)
{
}

これはAA(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;
};

5
必見は、参照の重要なケースにもある
4pie0

5
「a(3);」を使用しないのはなぜですか または「a = A(3);」Bのデフォルトコンストラクターの本体で?
セルゲイ

1
PODとはどういう意味ですか?
Jonas Stein

2
@JonasStein PODは、(完全なクラスではなく)単純なデータ構造に関する明確に定義された一連のルールです。詳細については、FAQをお読みください:stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506 2016年

2
@ Sergey、Aのデフォルトコンストラクターは引き続き呼び出されます。
Vassilis

44

上記のパフォーマンス上の理由とは別に、クラスがコンストラクターパラメーターとして渡されたオブジェクトへの参照を格納している場合、またはクラスにconst変数がある場合は、初期化リストを使用する以外に選択肢はありません。


7
私が信じているconstメンバーにも同じことが言えます。
Richard Corden、

はい、割り当てを使用してconst変数を変更できないため、初期化する必要があります。
Hareen Laks 2018

23
  1. 基本クラスの初期化

ここで回答に記載されていないコンストラクタ初期化リストを使用する重要な理由の1つは、基本クラスの初期化です。

構築の順序に従って、基本クラスは子クラスの前に構築する必要があります。コンストラクタ初期化子リストがない場合、これは、基本クラスに子クラスのコンストラクタに入る直前に呼び出されるデフォルトのコンストラクタがある場合に可能です。

ただし、基本クラスにパラメーター化されたコンストラクターのみがある場合は、コンストラクター初期化子リストを使用して、基本クラスが子クラスの前に初期化されるようにする必要があります。

  1. パラメータ化されたコンストラクタのみを持つサブオブジェクトの初期化

  2. 効率

コンストラクター初期化子リストを使用して、データメンバーを最初にデフォルト状態に初期化してから、その状態をコードで必要な状態に変更するのではなく、コードで必要な正確な状態に初期化します。

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

クラスの非静的constデータメンバーにデフォルトのコンストラクターがあり、コンストラクター初期化子リストを使用しない場合、それらをデフォルトの状態に初期化するため、意図した状態に初期化できません。

  1. 参照データメンバーの初期化

参照は単に宣言して後で初期化することはできないため、コンパイラーがコンストラクターに入るときに参照データメンバーを初期化する必要があります。これは、コンストラクタ初期化子リストでのみ可能です。


10

パフォーマンスの問題の隣に、コードの保守性と拡張性と呼ぶ非常に重要な問題があります。

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
};

「コードモンキー」ではなく、自分が何をしているかについてより深い考察に基づいて決定を下すエンジニアによってコードが記述されていれば、保守がはるかに簡単になり、エラーが発生しにくくなります。


5

コンストラクターの本体が実行される前に、その親クラスのすべてのコンストラクター、次にそのフィールドのコンストラクターが呼び出されます。デフォルトでは、引数のないコンストラクターが呼び出されます。初期化リストを使用すると、呼び出すコンストラクターとコンストラクターが受け取る引数を選択できます。

参照フィールドまたはconstフィールドがある場合、または使用されているクラスの1つにデフォルトのコンストラクタがない場合は、初期化リストを使用する必要があります。


2
// 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;
  1. そして、最後に「タイプ」のデストラクタが範囲外になるため、「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
    }
    };

    イニシャライザリストでは、次の手順の後にコンパイラが続きます。

    1. 「type」クラスのコピーコンストラクターが初期化するために呼び出されます:variable(a)。初期化子リストの引数は、「変数」構造を直接コピーするために使用されます。
    2. 「タイプ」のデストラクタは、範囲外になるため「a」に対して呼び出されます。

2
このコードスニペットは問題を解決する可能性がありますが、コードの説明を含めると、投稿の品質を向上させるのに役立ちます。あなたは将来の読者のための質問に答えていることを覚えておいてください、そしてそれらの人々はあなたのコード提案の理由を知らないかもしれません。また、コードと説明コメントを混同しないようにしてください。これにより、コードと説明の両方が読みにくくなります。meta.stackexchange.com/q/114762/308249
davejal

2
コピーして貼り付けるのではなく、理解を書くか、元のソース(ここではgeeksforgeeks.com)へのリンクを共有してください。
yuvi

1

メンバー初期化リストがどの程度の違いを生み出すことができるかを示すために、いくつかの追加情報を追加するだけです。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];
}
};

0

構文:

  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_xSam_yが作成されます。次に、コンストラクタ本体でそれらのメンバーデータ変数が定義されます。

ユースケース:

  1. クラスのConstおよびReference変数

Cでは、変数作成中に定義する必要あります。C ++と同じように、初期化リストを使用して、オブジェクトの作成中にConstおよびReference変数を初期化する必要があります。オブジェクトの作成後(コンストラクター本体の内部)に初期化を行うと、コンパイル時エラーが発生します。

  1. デフォルトのコンストラクタを持たない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)
  1. クラスコンストラクターのパラメーター名とクラスのデータメンバーは同じです。

     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.

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.