В C ++ 11 у нас есть новый синтаксис для инициализации классов, который дает нам большое количество возможностей для инициализации переменных.
{ // Example 1
int b(1);
int a{1};
int c = 1;
int d = {1};
}
{ // Example 2
std::complex<double> b(3,4);
std::complex<double> a{3,4};
std::complex<double> c = {3,4};
auto d = std::complex<double>(3,4);
auto e = std::complex<double>{3,4};
}
{ // Example 3
std::string a(3,'x');
std::string b{3,'x'}; // oops
}
{ // Example 4
std::function<int(int,int)> a(std::plus<int>());
std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
std::unique_ptr<int> a(new int(5));
std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
std::locale::global(std::locale("")); // copied from 22.4.8.3
std::locale::global(std::locale{""});
}
{ // Example 7
std::default_random_engine a {}; // Stroustrup's FAQ
std::default_random_engine b;
}
{ // Example 8
duration<long> a = 5; // Stroustrup's FAQ too
duration<long> b(5);
duration<long> c {5};
}
Для каждой объявленной переменной я должен подумать, какой синтаксис инициализации я должен использовать, и это замедляет мою скорость кодирования. Я уверен, что это не было намерением вводить фигурные скобки.
Когда дело доходит до кода шаблона, изменение синтаксиса может привести к различным значениям, поэтому важно идти правильным путем.
Интересно, есть ли универсальное руководство по выбору синтаксиса.
c++
c++11
initializer-list
Helami
источник
источник
Ответы:
Я думаю, что следующее может быть хорошим ориентиром:
Если (одиночное) значение, которое вы инициализируете, должно быть точным значением объекта, используйте
=
инициализацию copy ( ) (потому что тогда в случае ошибки вы никогда случайно не вызовете явный конструктор, который обычно интерпретирует предоставленное значение иначе). В местах, где инициализация копирования недоступна, проверьте, имеет ли инициализация скобками правильную семантику, и если да, используйте ее; в противном случае используйте инициализацию скобок (если она также недоступна, вам все равно не повезло).Если значения, которые вы инициализируете, представляют собой список значений, которые должны храниться в объекте (например, элементы вектора / массива или действительная / мнимая часть комплексного числа), используйте инициализацию фигурных скобок, если таковая имеется.
Если значения, которые вы инициализируете, не являются значениями для сохранения, а описывают предполагаемое значение / состояние объекта, используйте круглые скобки. Примерами являются аргумент размера или аргумент
vector
имени файлаfstream
.источник
T {}
или синтаксические причины, такие как самый неприятный синтаксический анализ ), но в целом я думаю, что это хороший совет. Обратите внимание, что это мое субъективное мнение, поэтому стоит взглянуть и на другие ответы.type var{};
делает.Я почти уверен, что универсального руководства никогда не будет. Мой подход - всегда использовать фигурные скобки, помня, что
Так что круглые и фигурные скобки не взаимозаменяемы. Но зная, где они различаются, я могу использовать инициализацию фигурных скобок в большинстве случаев (некоторые из случаев, когда я не могу этого сделать, в настоящее время являются ошибками компилятора).
источник
int i = 0;
Я не думаю, что кто-тоint i{0}
там будет использовать , и это может сбивать с толку (также,0
если типint
, так что сужения не будет ). Во всем остальном я бы последовал совету Хуанчо: предпочитайте {}, остерегайтесь тех немногих случаев, когда вам не следует этого делать. Обратите внимание, что не так много типов, которые будут принимать списки инициализаторов в качестве аргументов конструктора, вы можете ожидать, что контейнеры и контейнероподобные типы (кортеж ...) будут иметь их, но большая часть кода будет вызывать соответствующий конструктор.int i{some floating point}
это ошибка, а не тихо усекал.{}
для обозначения «инициализировать», если только вы не можете этого сделать .За пределами общего кода (например, шаблонов) вы можете (и я использую) везде использовать фигурные скобки . Одним из преимуществ является то, что он работает везде, например, даже для инициализации в классе:
struct foo { // Ok std::string a = { "foo" }; // Also ok std::string b { "bar" }; // Not possible std::string c("qux"); // For completeness this is possible std::string d = "baz"; };
или для аргументов функции:
void foo(std::pair<int, double*>); foo({ 42, nullptr }); // Not possible with parentheses without spelling out the type: foo(std::pair<int, double*>(42, nullptr));
Что касается переменных, которые я не уделяю особого внимания стилям
T t = { init };
илиT t { init };
, я считаю, что разница незначительна и в худшем случае приведет только к полезному сообщению компилятора о неправильном использованииexplicit
конструктора.Для типов, которые принимают,
std::initializer_list
хотя, очевидно, иногда необходимы неконструкторыstd::initializer_list
(классический примерstd::vector<int> twenty_answers(20, 42);
). Тогда нормально не использовать скобки.Когда дело доходит до универсального кода (например, в шаблонах), этот самый последний абзац должен вызывать некоторые предупреждения. Обратите внимание на следующее:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }
Затем
auto p = make_unique<std::vector<T>>(20, T {});
создает вектор размера 2, еслиT
, напримерint
, или вектор размера 20, еслиT
естьstd::string
. Ярким признаком того, что здесь происходит что-то очень неправильное, является то, что здесь нет черты, которая могла бы вас спасти (например, с SFINAE):std::is_constructible
это с точки зрения прямой инициализации, тогда как мы используем фигурную инициализацию, которая откладывает до прямого - инициализация тогда и только тогда, когда конструктор неstd::initializer_list
вмешивается. Точноstd::is_convertible
не помогает.Я исследовал, действительно ли можно вручную бросить черту, которая может это исправить, но я не слишком оптимистичен по этому поводу. В любом случае я не думаю, что мы чего-то упускаем, я думаю, что тот факт, что в
make_unique<T>(foo, bar)
результате будет эквивалентна конструкцияT(foo, bar)
, очень интуитивно понятен; особенно с учетом того, что ониmake_unique<T>({ foo, bar })
совершенно не похожи и имеют смысл только в том случае, если они имеют одинfoo
иbar
тот же тип.Следовательно, для общего кода я использую фигурные скобки только для инициализации значения (например,
T t {};
илиT t = {};
), что очень удобно и, по моему мнению, лучше, чем способ C ++ 03T t = T();
. В противном случае это либо прямой синтаксис инициализации (т.е.T t(a0, a1, a2);
), либо иногда конструкция по умолчанию (T t; stream >> t;
я думаю, это единственный случай, когда я использую это).Это не значит, что все фигурные скобки плохие, рассмотрим предыдущий пример с исправлениями:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }
При этом фигурные скобки по-прежнему используются для построения
std::unique_ptr<T>
, хотя фактический тип зависит от параметра шаблонаT
.источник
make_unique<T>(20u, T {})
дляT
того, чтобы быть либоunsigned
илиstd::string
. Не слишком уверен в деталях. (Обратите внимание, что я также прокомментировал ожидания в отношении прямой инициализации и инициализации скобок, например, в отношении функций точной пересылки.)std::string c("qux");
Не было указано, что она работает как инициализация класса, чтобы избежать двусмысленности с объявлениями функций-членов в грамматике.