В чем преимущество разных вариантов инициализации и в чем особенность инициализации {} от ()
int a = 5;
int a(5);
int a{5};
int a = 5;
int a(5);
int a{5};
Рассмотрим разницу кратко. Допустим у нас есть некий класс, и пишем явно
test x = 5;
что бы это отработало, класс должен иметь конструктор вида
test(int x) {}
если же сделать конструктор explicit, то это уже работать не будет.
explicit test(int x) {}
строка
test x(5);
хочет точно такого же конструктора, но будет работать с обеими конструкторами. И ровно так, как ожидается.
строка
test{5};
это уже новомодное изобретение (с++11). Хочет специального конструктора
test(std::initializer_list<int> x) {}
Но этот конструктор понимает и такое
test x{1,2,3,4};
что иногда бывает очень сильно удобно.
С подобной разницей можно встретиться в случае std::vector. конструкция std::vector<int> x(5);
создаст вектор на 5 элементов с нулями (дефолтное значение для int), а std::vector<int> x{5};
создаст вектор на один элемент со значением 5.
Почему же многие рекомендуют способ со скобками? видимо как раз по этой причине. Им надоело, что вектор создает именно несколько элементов (что некоторые считают неочевидным, так как int x(5)
делает именно инициализацию) и выработали для себя привычку писать в фигурных скобках. Естественно ее и пропагандируют. Это точно также как известное условие Йоды - современные компиляторы давно научились бить по пальцам за присваивание, но программисты продолжают писать по старому.
Как же писать в реальном коде? пишите так, как требует код-стайл. Но для простых типов я все же писал просто через присваивание.
В первом случае - все прозрачно - это инициализация с помощью литерала (скорее всего я забыл и "по науке" это называется иначе, но не суть). Подобная инициализация применима только к базовым типам (тот же int, double и тд), поскольку только для них существуют литералы. Этот способ удобен своим кратким синтаксисом
Во втором случае используется конструктор (да, у базовых типпов тоже есть конструкторы) Разницы в принципе никакой (поправьте, если вдруг ошибаюсь, но вот этот замечательный сервис показывает, что в обоих случаях генерируется одинаковый код). Преимуществ по сравнению с первым типом нет, но выглядит длиннее и чуть менее интуитивно (в случае с базовыми типами), а потому чаще используется первый вариант.
Третий вариант - это использование списка инициализации, возможности которых были существенно расширены в C++11. В этом случае есть разница - если при использовании первых двух способов вы вместо int подсунете например double, то компилятор вам ничего не скажет, а просто неявно преобразует его к int. Однако, используя третий вариант, вы увидите ошибку. Например:
int a1 = 5.5; // ошибки нет
int a2(5.5); // ошибки нет
int a3{ 5.5 }; // ошибка компиляции
Добавлю еще, что фигурные скобки запрещают применение сужающего преобразования, т.е. если значение может не поместиться в типе.
Например,
int x = 5;
char c(x); // Компилятор может предупредить о сужающем преобразовании
char s{x}; // Компилятор обязан запретить эту инициализацию