Правильно инициализируя переменные в современном С++ (С++ 11 и выше), используя() или {}?
справочные страницы С++ говорят, что() для инициализации значения, {} для значения и агрегата и инициализации списка. Итак, если я просто хочу инициализацию значения, какой из них я использую?() или {}? Я спрашиваю, потому что в книге "A Tour of С++" самого Бьярне он предпочитает использовать {} даже для инициализации значения (см., Например, страницы 6 и 7), и поэтому я думал, что это хорошая практика всегда используйте {}, даже для инициализации значения. Тем не менее, я был сильно укушен следующей ошибкой в последнее время. Рассмотрим следующий код.
auto p = std::make_shared<int>(3);
auto q{ p };
auto r(p);
Теперь, согласно компилятору (Visual Studio 2013), q
имеет тип std::initializer_list<std::shared_ptr<int>>
, что не то, что я намеревался. То, что я на самом деле предназначалось для q
, на самом деле таково r
, которое std::shared_ptr<int>
. Поэтому в этом случае я не должен использовать {} для инициализации значения, но используйте(). Учитывая это, почему Бьярне в своей книге по-прежнему предпочитает использовать {} для инициализации значения? Например, он использует double d2{2.3}
в нижней части страницы 6.
Чтобы окончательно ответить на мои вопросы, когда следует использовать() и когда следует использовать {}? И это вопрос правильности синтаксиса или вопрос хорошей практики программирования?
О, и, если возможно, просто по-английски.
EDIT:
Кажется, что я немного неправильно понял инициализацию стоимости (см. Ответы ниже). Однако вышеприведенные вопросы по-прежнему сохраняются.
Ответы
Ответ 1
Это мое мнение.
При использовании auto
в качестве спецификатора типа, он более чистый, чтобы использовать:
auto q = p; // Type of q is same as type of p
auto r = {p}; // Type of r is std::initializer_list<...>
При использовании явного спецификатора типа лучше использовать {}
вместо ()
.
int a{}; // Value initialized to 0
int b(); // Declares a function (the most vexing parse)
Можно использовать
int a = 0; // Value initialized to 0
Однако форма
int a{};
можно также использовать для инициализации объектов определенных пользователем типов. Например.
struct Foo
{
int a;
double b;
};
Foo f1 = 0; // Not allowed.
Foo f1{}; // Zero initialized.
Ответ 2
Скотт Майерс справедливо оценивает разницу между двумя методами инициализации в своей книге Эффективный современный С++.
Он суммирует оба подхода следующим образом:
Большинство разработчиков в конечном итоге выбирают один вид разделителя как значение по умолчанию, используя другой только тогда, когда они должны. Люди с привязкой по умолчанию привлеченные их непревзойденной широтой применимости, их запрет сужения конверсий и их иммунитет к С++ досадный анализ. Такие люди понимают, что в некоторых случаях (например, создание от std::vector
с заданным размером и значением начального элемента), требуются скобки. С другой стороны, круглые скобки идут толпа объявляет круглые скобки как разделитель аргументов по умолчанию. Theyre привлекли его соответствие синтаксису С++ 98 традиция, его избегание файла aut-deduced-a-std:: initializer_list проблемы и знания о том, что их создание объектов не будет непреднамеренно уклоняются конструкторами std::initializer_list
. Oни согласитесь, что иногда будут выполняться только брекеты (например, при создании контейнер с определенными значениями). Не существует единого мнения о том, что либо подход лучше, чем другой, поэтому мой совет - выбрать один и применяйте его последовательно.
Ответ 3
Во-первых, кажется, что терминологический микс. У вас нет инициализации значения. Инициализация значения происходит, когда вы не предоставляете явных аргументов инициализации. int x;
использует инициализацию по умолчанию, значение x
будет неуказано. int x{};
использует инициализацию значения, x
будет 0
. int x();
объявляет функцию — поэтому {}
является предпочтительным для инициализации значения.
Код, который вы указали, не использует инициализацию значения. При auto
наиболее безопасным является использование инициализации копирования:
auto q = p;
Ответ 4
Существует еще одно важное отличие: инициализатор скобок требует, чтобы данный тип действительно мог удерживать заданное значение. Другими словами, он запрещает сужение значения, например округление или усечение.
int a(2.3); // ok? a will hold the value 2, no error, maybe compiler warning
uint8_t c(256); // ok? the compiler should warn about something fishy going on
По сравнению с инициализацией скобки
int A{2.3}; // compiler error, because int can NOT hold a floating point value
double B{2.3}; // ok, double can hold this value
uint8_t C{256}; // compiler error, because 8bit is not wide enough for this number
В частности, при типичном программировании с шаблонами вы должны использовать инициализацию скобок, чтобы избежать неприятных неожиданностей, когда базовый тип делает что-то неожиданное для ваших входных значений.
Ответ 5
{} - инициализация значения, если она пуста, если не инициализация списка/агрегата.
Из черновика, 7.1.6.4 автоспециалист, 7/... Пример,
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
Правила немного сложны для объяснения здесь (даже трудно прочитать из источника!).
Ответ 6
Трава Саттер, похоже, делает аргумент в CppCon 2014 (39:25 в разговоре) для использования инициализаторов auto и brace, например
auto x = MyType { initializers };
когда вы хотите принудить тип, для согласования слева направо в определениях:
- Тип-вывод:
auto x = getSomething()
- Тип-принуждение:
auto x = MyType { blah }
- Пользовательские литералы
auto x = "Hello, world."s
- Объявление функции:
auto f { some; commands; } -> MyType
- Именованный Лямбда:
using auto f = [=]( { some; commands; } -> MyType
- С++ 11-стиль typedef:
using AnotherType = SomeTemplate<MyTemplateArg>
Ответ 7
Скотт Майерс только что разместил соответствующую запись в блоге Мысли о капризах инициализации С++. Кажется, что С++ по-прежнему имеет путь к достижению действительно единообразного синтаксиса инициализации.