Почему я должен предпочитать использовать список инициализации членов?

Я частично использую списки инициализации членов с моими конструкторами... но я давно забыл причины этого...

используете ли вы списки инициализации членов в своих конструкторах? Если так, то почему? Если нет, то почему?

8 ответов


на 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)
{
}

это только вызов A ' s 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. инициализация базового класса

одной из важных причин использования списка инициализаторов конструктора, который не упоминается в ответах, является инициализация базового класса.

согласно порядку конструкции, базовый класс должен быть построен перед дочерним классом. Без списка инициализаторов конструктора, это возможно, если ваш базовый класс имеет конструктор по умолчанию, который будет вызываться непосредственно перед входом в конструктор ребенок класс.

но если ваш базовый класс имеет только параметризованный конструктор, то вы должны использовать список инициализаторов конструктора, чтобы убедиться, что ваш базовый класс инициализирован перед дочерним классом.

  1. инициализация подобъектов, которые имеют только параметризованные конструкторы

  2. эффективность

используя список инициализаторов конструктора, вы инициализируете свои члены данных в точное состояние что вам нужно в коде, а не сначала инициализировать их в состояние по умолчанию, а затем изменить их состояние на то, которое вам нужно в коде.

  1. инициализация нестатических членов данных const

если нестатические члены данных const в вашем классе имеют конструкторы по умолчанию, и вы не используете список инициализаторов конструктора, вы не сможете инициализировать их в предполагаемое состояние, поскольку они будут инициализированы по умолчанию государство.

  1. инициализация членов ссылочных данных

члены ссылочных данных должны быть intialized когда компилятор входит конструктор как ссылки не могут быть просто объявлены и инициализированы позже. Это возможно только с помощью списка инициализаторов конструктора.


рядом с проблемами производительности есть еще один очень важный, который я бы назвал ремонтопригодностью и расширяемостью кода.

Если T-POD, и вы начинаете предпочитать список инициализации, то если один раз T изменится на тип без POD, вам не нужно будет ничего менять вокруг инициализации, чтобы избежать ненужных вызовов конструктора, потому что он уже оптимизирован.

Если тип T имеет конструктор по умолчанию и один или несколько пользовательских конструкторов и один если вы решили удалить или скрыть список по умолчанию, то если использовался список инициализации, вам не нужно обновлять код, если ваши пользовательские конструкторы, потому что они уже правильно реализованы.

то же самое с членами const или ссылочными членами, скажем, изначально T определяется следующим образом:

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

затем вы решите квалифицировать a как const, если вы будете использовать список инициализации с самого начала, то это было изменение одной строки, но с T, определенным как выше, он также требует, чтобы копать определение конструктора, чтобы удалить назначение:

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

Не секрет, что обслуживание намного проще и менее подвержено ошибкам, если код был написан не "обезьяной кода", а инженером, который принимает решения, основанные на более глубоком рассмотрении того, что он делает.


перед запуском тела конструктора вызываются все конструкторы для его родительского класса, а затем для его полей. По умолчанию вызываются конструкторы без аргументов. Списки инициализации позволяют выбрать, какой конструктор вызывается и какие аргументы получает конструктор.

Если у вас есть ссылка или поле const или если один из классов не имеет конструктор по умолчанию, вы должны использовать список инициализации.


// 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", поскольку он выходит за рамки.

    Теперь рассмотрим тот же код с конструктором 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). Аргументы в списке инициализаторов используются для копирования конструкции "variable" напрямую.
    2. деструктор " типа "вызывается для" a", поскольку он выходит за рамки.

синтаксис:

  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. Const и ссылочные переменные в классе

в 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() конструктор и член класса переменной(я) получил переопределить. Чтобы избежать, мы должны использовать либо

  1. Initialization list 
  2. this operator.

просто чтобы добавить дополнительную информацию, чтобы продемонстрировать, насколько разница в списке инициализации члена может mak. В запросе суммы диапазона leetcode 303-неизменяемый,https://leetcode.com/problems/range-sum-query-immutable/, где вам нужно построить и инициализировать до нуля вектор с определенным размером. Вот две разные реализации и сравнение скорости.

без инициализации членов список, чтобы получить 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 МС. На этом простом примере совершенно очевидно, что,список инициализации членов более эффективным!--4-->. Все измерение от времени выполнения от 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];
}
};