Инициализация 1/2.
Как-то так получилось, что в плюсах даже инициализация довольно тяжкая тема. Как минимум потому что их огромное количество. К счастью, в 99% случаев вы об этом не думаете. Не думаете, какие отличия несут в себе разные способы инициализации. Но на случай, если вы вдруг встретитесь, давайте немного посмотрим на разные виды и их отличия.
Первый пример:
int i;return i; // ub
struct S {
int x;
};
S s;
return s.x; // ubЭто default initialisation, что вообще-то немного путает, потому что переменная у вас не инициализируется ничем дефолтным (мусор я бы дефолтным не назвал). Можно поправить так:
S() : x(0) {}Это member initialiser list. Не оч удобно, потому что надо повторять во всех конструкторах, потому в C++11 появился
struct S {
int x = 0; // default member initialiser
};Наверное использовать dmi может быть хорошим советом, пусть и не лишённым проблем.
Далее:
int i = 2; // copy initialisationТакой же способ инициализации используется, когда вы передаёте аргумент в функцию по значению или по значению возвращаете. Если тип инициализируемого объекта и значения справа от = не совпадают, будет произведена некоторая цепочка преобразований типов (насколько я понимаю, сколько угодно стандартных преобразований и может быть одно пользовательское, всё это в произвольном порядке, конечно).
Помните, что copy init это никогда не
operator=. Aggregate init:
int i[3] = {1, 2, 3};
int i[] = {1, 2, 3};
struct S { int x; int y; };
auto s{1, 2};Агрегатная инициализация будет copy-инициализировать каждый из переданных аргументов. У такого init есть фича:
struct S { int x; int y; };
S s = {1};
return s.y; // какое значение? ub?Если вы опускаете часть аргументов при использовании агрегатной инициализации, остальные будут zero initialisated. Т.е. у вас никогда при таком способе не будет неинициализированных данных. Потому и работает вот такой способ заполнения массивов нулями:
int a[10] = {};Ещё тут есть такая фича как brace elision.
Далее static initialisation:
static int i = 1; // constant initialisation
static int j; // zero initialisationВсе эти способы были просто унаследованы плюсами от C. Теперь про плюсовые.
Direct initialisations (whenever the initialiser is an argument list in parens):
S s(1, 2); // constructor
int i(3); // also for built-in typeДля built-in типов никакой разницы от copy init, но для остальных это возможность передать более одного аргумента при инициализации и отсутствие цепочки преобразований (просто вызов конструктора по всем правилам перегрузок). Тут стоит быть осторожным:
struct S {
explicit S(int) {}
S(double) {}
};
S s1 = 1; // S(double)
S s2(2); // S(int)Value init (whenever the initialiser is a pair of empty parens):
int f() { return int(); }Если есть пользовательский default ctor, то вызовется он, иначе zero init. Тут можно словить всякие маслины. Осторожно.
Uniform init — способ универсальной инициализации из C++11 (с помощью {}). Тут же появились list inits:
S s{1, 2}; // direct list init
S s = {1, 2}; // copy list initИ тут же появился
std::initializer_list. Думаю, как им пользоваться вы знаете, так что опустим. Про его проблемы можно глянуть тут. Для агрегатов list init это agregate init. Для built-in типов пример выше. Для классов либо вызов подходящего конструктора, либо direct/copy init.
{} — особый случай. Это дефолтный ктор, иначе ктор от initializer_list, иначе value init ☺️🎧При list init никаких narrowing conversions не происходит, так что
int i{1.0} не закатит.Ну и опуская всякое неинтересное, не забудем про C++20 designated initialisation:
struct S { int x; int y; };
S s{.x = 1, .y = 2};Подробнее со всякими интересными примерами можете посмотреть в доклад Тимура Думлера.
Тут мы упомянули даже не всё что на гифке. А на гифке тоже пары штук не хватает🙂