Почему я предпочитаю использовать список инициализации членов? - PullRequest
202 голосов
/ 29 мая 2009

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

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

Ответы [ 8 ]

252 голосов
/ 29 мая 2009

Для 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 конструктор 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;
};
41 голосов
/ 29 мая 2009

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

16 голосов
/ 02 декабря 2016
  1. Инициализация базового класса

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

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

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

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

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

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

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

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

  1. Инициализация элементов справочных данных

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

8 голосов
/ 21 августа 2009

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

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

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

То же самое с константными членами или ссылочными элементами, скажем, изначально 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
};

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

5 голосов
/ 10 июня 2011

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

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

2 голосов
/ 25 ноября 2015
// 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. Конструктор типа сначала вызывается как «а».
2. Оператор присваивания «Type» вызывается внутри тела конструктора MyClass () для присвоения

variable = a;
  1. И, наконец, деструктор типа «Type» вызывается как «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. Конструктор копирования класса «Тип» вызывается для инициализации: переменная (а). Аргументы в списке инициализатора используются для непосредственного копирования конструкции «переменная».
    2. Деструктор «Типа» вызывается как «а», так как он выходит из области видимости.
1 голос
/ 03 сентября 2018

Просто добавьте некоторую дополнительную информацию, чтобы продемонстрировать, какую разницу может составить список инициализации члена . В leetcode 303 Range Sum Query - Immutable, 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 мс . На этом простом примере совершенно очевидно, что список инициализации членов гораздо эффективнее . Все измерения от времени выполнения от 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 голосов
/ 28 июля 2017

Синтаксис:

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

В C переменные должны быть определены при создании. Точно так же в C ++ мы должны инициализировать переменную Const и Reference во время создания объекта с помощью списка инициализации. если мы сделаем инициализацию после создания объекта (внутри тела конструктора), мы получим ошибку времени компиляции.

  1. Объекты-члены класса Sample1 (base), которые не имеют конструктора по умолчанию

     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. Имя параметра конструктора класса и член Data класса одинаковы:

     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» {как левую, так и правую переменную. i.e: i = i} как локальная переменная в конструкторе Sample3 (), а переменная-член класса (i) получила переопределение. Чтобы избежать, мы должны использовать либо

  1. Initialization list 
  2. this operator.
...