С++ Инициализация полей непосредственно к списку инициализации в конструкторе по умолчанию
Я хотел бы знать, есть ли разница между этим кодом:
class Foo{
private:
int a = 0;
public:
Foo(){}
}
и
class Foo{
private:
int a;
public:
Foo(): a(0) {}
}
И если да, то что должно быть предпочтительнее?
Я знаю, что предпочтительнее использовать список инициализаторов, чем назначать в теле конструктора, но что относительно списка инициализаторов против прямой инициализации в объявлении поля (для примитивных типов, по крайней мере, как это имеет место здесь)?
Кроме того, что касается нижеприведенного случая:
class Foo{
private:
int a = 0;
public:
Foo(){}
Foo(int i): a(i) {}
}
Когда вызывается нестандартный конструктор: "а" инициализируется дважды, сначала до 0, затем до "i" или непосредственно к "i"?
Ответы
Ответ 1
От cppreference - нестатические элементы данных
Инициализация члена
1) В списке инициализатора члена конструктора.
2) Через инициализатор элемента по умолчанию, который является просто скобкой или равен инициализатору, включенному в декларацию члена, который используется, если член опущен в списке инициализаторов членов.
Если член имеет инициализатор элемента по умолчанию, а также появляется в списке инициализации члена в конструкторе, инициализатор элемента по умолчанию игнорируется.
В заключение, оба инициализатора эквивалентны и выполняют то, что они должны делать.
Я бы предпочел инициализатор элемента по умолчанию, если бы я использовал конструктор по умолчанию в любом случае, или если все или большинство конструкторов инициализировали член с тем же значением.
class Foo {
private:
int a = 0;
};
Если все конструкторы инициализируют элемент до некоторого другого значения, использование элемента инициализатора по умолчанию имеет меньшее значение, а затем явная инициализация в соответствующих конструкторах будет более понятной.
class Foo {
private:
int a;
public:
Foo() : a(3) {}
Foo(int i) : a(i) {}
};
Ответ 2
Первый набор примеров идентичен друг другу.
В последнем примере стандарт С++ указывает следующее:
12.6.2 Инициализация баз и членов
[...]
Если данный нестатический член данных имеет как скользящий или равный-инициализатор и mem-инициализатор, инициализация заданный mem-инициализатором, и нестатические данные Элемент-член-бит-или-равный-инициализатор игнорируется. [Пример: данный
struct A {
int i = /∗ some integer expression with side effects ∗/ ;
A(int arg) : i(arg) { }
// ...
};
конструктор A (int) просто инициализирует я значением arg, и побочные эффекты в состоянии, равные-равному-инициализатору, не будут место. - конец примера]
Ответ 3
Оба идентичны.
Одно из правил разработки программного обеспечения DRY - не повторяйте себя. DRY указывает, что если вы можете не повторять один и тот же токен дважды или иметь два идентичных списка, вы должны.
Это по нескольким причинам. Поддержание двух идентичных списков удивительно подвержено ошибкам; один изменен или имеет опечатку, а другой нет. Это делает код длиннее, что затрудняет его чтение. И, избегая кодирования вставки, поощряется использование некоторых очень мощных и выразительных методов, которые могут сделать то, что вы делаете яснее, чем делать это вручную 17 раз.
struct foo {
int a;
foo():a(7) {}
};
здесь мы повторили себя - список переменных-членов, в частности, указан дважды. Один раз в определении foo
и снова в списке инициализаторов foo::foo
. Если он где-то отсутствует, вы получаете неинициализированные данные.
struct foo {
int a = 7;
foo() {}
};
Здесь мы не повторяемся.
struct foo {
int a = 7;
foo() {}
foo(int i):a(i) {}
};
Здесь есть некоторое повторение, но повторение неизбежно. Это, однако, сведено к минимуму.
Здесь есть некоторая стоимость, потому что кто-то может интерпретировать a=7
как "он всегда начинается с 7", а не "по умолчанию - 7".
struct foo {
int a = 7;
foo():a(3) {}
foo(int i):a(i) {}
};
И выше это ужасный анти-шаблон.