Я не согласен с использованием списков инициализации членов с моими конструкторами... но я уже давно забыл причины этого...
Используете ли вы списки инициализации членов в своих конструкторах? Если да, то почему? Если нет, почему бы и нет?
Я не согласен с использованием списков инициализации членов с моими конструкторами... но я уже давно забыл причины этого...
Используете ли вы списки инициализации членов в своих конструкторах? Если да, то почему? Если нет, почему бы и нет?
Для 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;
};
Помимо упомянутых выше причин производительности, если ваш класс хранит ссылки на объекты, переданные как параметры конструктора, или ваш класс имеет константные переменные, то у вас нет выбора, кроме использования списков инициализаций.
Одной из важных причин использования списка инициализаторов конструктора, который здесь не упоминается, является инициализация базового класса.
В соответствии с порядком построения базовый класс должен быть создан до дочернего класса. Без списка инициализаторов конструктора это возможно, если ваш базовый класс имеет конструктор по умолчанию, который будет вызываться непосредственно перед вводом конструктора дочернего класса.
Но если ваш базовый класс имеет только параметризованный конструктор, вы должны использовать список инициализаторов конструктора, чтобы убедиться, что ваш базовый класс инициализирован перед дочерним классом.
Инициализация субообъектов, которые имеют только параметризованные конструкторы
Эффективность
Используя список инициализаторов конструктора, вы инициализируете своих членов данных точным состоянием, которое вам нужно в коде, а не сначала инициализируйте их до состояния по умолчанию, а затем измените их состояние на тот, который вам нужен в вашем коде.
Если нестатические члены const-данных в вашем классе имеют конструкторы по умолчанию, и вы не используете список инициализаторов конструктора, вы не сможете инициализировать их до предполагаемого состояния, поскольку они будут инициализированы в их состоянии по умолчанию.
Элементы ссылочных данных должны быть инициализированы, когда компилятор вводит конструктор, поскольку ссылки не могут быть просто объявлены и инициализированы позже. Это возможно только с помощью списка инициализаторов конструктора.
Рядом с проблемами производительности есть еще одна очень важная проблема, которую я бы назвал легкостью и расширяемостью кода.
Если T - это POD, и вы начинаете предпочитать список инициализации, то если одно время T изменится на не-POD-тип, вам не нужно ничего менять во время инициализации, чтобы избежать ненужных вызовов конструктора, потому что он уже оптимизирован.
Если тип T имеет конструктор по умолчанию и один или несколько определяемых пользователем конструкторов, и один раз вы решили удалить или скрыть значение по умолчанию, тогда, если был использован список инициализации, вам не нужно обновлять код, определенные конструкторы, потому что они уже правильно реализованы.
То же самое с константными членами или ссылочными элементами, скажем, первоначально Т определяется следующим образом:
struct T
{
T() { a = 5; }
private:
int a;
};
Затем вы решили квалифицировать как const, если бы вы использовали список инициализации с самого начала, то это было однострочное изменение, но с T, как указано выше, также требуется выкопать определение конструктора для удаления назначения
struct T
{
T() : a(5) {} // 2. that requires changes here too
private:
const int a; // 1. one line change
};
Не секрет, что обслуживание намного проще и менее подвержено ошибкам, если код был написан не "обезьяной кода", а инженером, который принимает решения, основываясь на более глубоком рассмотрении того, что он делает.
Прежде чем запустить тело конструктора, вызывается все конструкторы для его родительского класса, а затем для его полей. По умолчанию вызываются конструкторы без аргументов. Списки инициализации позволяют вам выбрать, какой конструктор вызывается и какие аргументы получает конструктор.
Если у вас есть ссылка или константное поле, или если один из используемых классов не имеет конструктора по умолчанию, вы должны использовать список инициализации.
// 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. Оператор присваивания "Тип" вызывается внутри тела конструктора MyClass() для назначения
variable = a;
И тогда, наконец, деструктор "Тип" вызывается для "a" , поскольку он выходит за рамки.
Теперь рассмотрим тот же код с конструктором MyClass() с Initializer List
// 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
}
};
В списке инициализаторов последующие шаги следуют за компилятором:
Просто добавьте дополнительную информацию, чтобы продемонстрировать, насколько сильно отличается список инициализации членов. В 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];
}
};
Синтаксис:
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. Затем в тело конструктора определяются эти переменные данных элемента.
Варианты использования:
В C переменные должны быть определены во время создания. то же самое в С++, мы должны инициализировать переменную Const и Reference во время создания объекта, используя список инициализации. если мы выполняем инициализацию после создания объекта (внутри тела конструктора), мы получим ошибку времени компиляции.
Объекты-члены класса 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)
Имя параметра конструктора классов и элемент данных класса одинаковы:
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} как локальная переменная в конструкторе Sample3(), а переменная-член класса (i) переопределена. Чтобы избежать этого, мы должны использовать либо
1. Initialization list
2. this operator.