web-dev-qa-db-ja.com

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

私は自分のコンストラクタでメンバー初期化リストを使用することに部分的に取り組んでいます...

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

205
paxos1977

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のコンストラクターはAのデフォルトコンストラクターを呼び出してから、a.xを3に初期化します。より良い方法は、Bのコンストラクターを直接呼び出すことです初期化子リストのAのコンストラクター:

B()
  : a(3)
{
}

これはAA(int)コンストラクターのみを呼び出し、デフォルトのコンストラクターは呼び出しません。この例では、違いは無視できますが、Aのデフォルトコンストラクターがメモリの割り当てやファイルのオープンなど、より多くのことをしたとしたらどうでしょうか。不必要にそれをしたくはないでしょう。

さらに、クラスにデフォルトのコンストラクターがない場合、またはconstメンバー変数がある場合、mustは初期化リストを使用します。

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;
};
257
Adam Rosenfield

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

41
Naveen
  1. 基本クラスの初期化

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

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

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

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

  2. 効率

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

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

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

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

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

16
yuvi

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

TがPODであり、初期化リストの優先を開始する場合、一度Tが非POD型に変​​更される場合、すでに最適化されているため、不要なコンストラクター呼び出しを回避するために初期化周辺で何かを変更する必要はありません。

タイプTにデフォルトコンストラクターと1つ以上のユーザー定義コンストラクターがあり、デフォルトコンストラクターを削除または非表示にすることを決定した場合、初期化リストが使用されていれば、ユーザー定義コンストラクターの場合、コードを更新する必要はありません。それらはすでに正しく実装されています。

Constメンバーまたは参照メンバーと同様に、最初にTが次のように定義されているとします。

struct T
{
    T() { a = 5; }
private:
    int a;
};

次に、初期化リストを最初から使用する場合、constとして修飾することにします。これは1行の変更でしたが、上記のようにTが定義されているため、割り当てを削除するにはコンストラクター定義を掘る必要があります:

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

「コードモンキー」ではなく、自分が何をしているのかをより深く考慮して決定を下すエンジニアがコードを記述した場合、メンテナンスがはるかに簡単でエラーが発生しにくいことは秘密ではありません。

8
mloskot

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

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

5
Jamal Zafar
// 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. そして最後に、「Type」のデストラクタが「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
Rahul Singh

追加の情報を追加して、メンバー初期化リストがmakとどれだけ異なるかを示すだけです。 leetcode 303 Range Sum Query-Immutable、 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 ms。この簡単な例では、メンバー初期化リストの方がはるかに効率的ですことは明らかです。すべての測定は、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];
}
};
1
Yi Wang

構文:

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

ユースケース:

  1. クラスの定数と参照変数

Cでは、変数mustは作成時に定義されます。 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}およびClassメンバーvariable(i)がオーバーライドされました。回避するには、次のいずれかを使用する必要があります

  1. Initialization list 
  2. this operator.
0
Eswaran Pandi