Когда использовать инициализатор в фигурных скобках?

97

В 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};
}

Для каждой объявленной переменной я должен подумать, какой синтаксис инициализации я должен использовать, и это замедляет мою скорость кодирования. Я уверен, что это не было намерением вводить фигурные скобки.

Когда дело доходит до кода шаблона, изменение синтаксиса может привести к различным значениям, поэтому важно идти правильным путем.

Интересно, есть ли универсальное руководство по выбору синтаксиса.

Helami
источник
1
Пример непреднамеренного поведения при инициализации {}: строка (50, 'x') vs строка {50, 'x'} здесь
P i

Ответы:

65

Я думаю, что следующее может быть хорошим ориентиром:

  • Если (одиночное) значение, которое вы инициализируете, должно быть точным значением объекта, используйте =инициализацию copy ( ) (потому что тогда в случае ошибки вы никогда случайно не вызовете явный конструктор, который обычно интерпретирует предоставленное значение иначе). В местах, где инициализация копирования недоступна, проверьте, имеет ли инициализация скобками правильную семантику, и если да, используйте ее; в противном случае используйте инициализацию скобок (если она также недоступна, вам все равно не повезло).

  • Если значения, которые вы инициализируете, представляют собой список значений, которые должны храниться в объекте (например, элементы вектора / массива или действительная / мнимая часть комплексного числа), используйте инициализацию фигурных скобок, если таковая имеется.

  • Если значения, которые вы инициализируете, не являются значениями для сохранения, а описывают предполагаемое значение / состояние объекта, используйте круглые скобки. Примерами являются аргумент размера или аргумент vectorимени файла fstream.

Celtschk
источник
4
@ user1304032: языковой стандарт не является строкой, поэтому вы не будете использовать инициализацию копирования. Локаль также не содержит строки (она может сохранять эту строку как деталь реализации, но это не ее цель), поэтому вы не будете использовать инициализацию скобок. Поэтому в руководстве рекомендуется использовать инициализацию скобок.
celtschk 02
2
Мне лично больше всего понравилось это руководство, и оно также хорошо работает с общим кодом. Есть некоторые исключения ( T {}или синтаксические причины, такие как самый неприятный синтаксический анализ ), но в целом я думаю, что это хороший совет. Обратите внимание, что это мое субъективное мнение, поэтому стоит взглянуть и на другие ответы.
helami
2
@celtschk: Это не сработает для не копируемых и неподвижных типов; type var{};делает.
ildjarn 02
2
@celtschk: Я не говорю, что это что-то, что будет происходить часто, но это меньше печатает и работает в большем количестве контекстов, так в чем же обратная сторона?
ildjarn
2
Мои рекомендации, конечно, никогда не требуют инициализации копирования. ; -]
ildjarn 03
27

Я почти уверен, что универсального руководства никогда не будет. Мой подход - всегда использовать фигурные скобки, помня, что

  1. Конструкторы списка инициализаторов имеют приоритет над другими конструкторами
  2. Все стандартные библиотечные контейнеры и std :: basic_string имеют конструкторы списка инициализаторов.
  3. Инициализация фигурных скобок не позволяет сужать преобразования.

Так что круглые и фигурные скобки не взаимозаменяемы. Но зная, где они различаются, я могу использовать инициализацию фигурных скобок в большинстве случаев (некоторые из случаев, когда я не могу этого сделать, в настоящее время являются ошибками компилятора).

чуанчопанза
источник
7
У фигурных скобок есть тот недостаток, что я могу по ошибке вызвать конструктор списка. Круглые скобки - нет. Разве это не повод по умолчанию использовать круглые скобки?
helami 02
4
@user: int i = 0;Я не думаю, что кто-то int i{0}там будет использовать , и это может сбивать с толку (также, 0если тип int, так что сужения не будет ). Во всем остальном я бы последовал совету Хуанчо: предпочитайте {}, остерегайтесь тех немногих случаев, когда вам не следует этого делать. Обратите внимание, что не так много типов, которые будут принимать списки инициализаторов в качестве аргументов конструктора, вы можете ожидать, что контейнеры и контейнероподобные типы (кортеж ...) будут иметь их, но большая часть кода будет вызывать соответствующий конструктор.
Дэвид Родригес - dribeas
3
@ user1304032 это зависит от того, заботитесь ли вы о сужении. Да, поэтому я предпочитаю, чтобы компилятор сказал мне, что int i{some floating point}это ошибка, а не тихо усекал.
juanchopanza 02
3
Что касается «предпочитать {}, остерегайтесь нескольких случаев, когда вы не должны»: скажем, два класса имеют семантически эквивалентный конструктор, но один класс также имеет список инициализаторов. Следует ли называть два эквивалентных конструктора по-разному?
helami 02
3
@helami: «Допустим, у двух классов есть семантически эквивалентный конструктор, но у одного класса тоже есть список инициализаторов. Следует ли вызывать два эквивалентных конструктора по-разному?» Скажем, я наткнулся на самый неприятный разбор; это может произойти с любым конструктором для любого экземпляра. Гораздо легче избежать этого, если вы просто используете {}для обозначения «инициализировать», если только вы не можете этого сделать .
Никол Болас
16

За пределами общего кода (например, шаблонов) вы можете (и я использую) везде использовать фигурные скобки . Одним из преимуществ является то, что он работает везде, например, даже для инициализации в классе:

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 ++ 03 T 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.

Люк Дантон
источник
@interjay В некоторых моих примерах может действительно потребоваться использование беззнаковых типов, например, make_unique<T>(20u, T {})для Tтого, чтобы быть либо unsignedили std::string. Не слишком уверен в деталях. (Обратите внимание, что я также прокомментировал ожидания в отношении прямой инициализации и инициализации скобок, например, в отношении функций точной пересылки.) std::string c("qux");Не было указано, что она работает как инициализация класса, чтобы избежать двусмысленности с объявлениями функций-членов в грамматике.
Люк Дантон
@interjay Я не согласен с вами по первому пункту, не стесняйтесь проверить 8.5.4 Инициализация списка и 13.3.1.7 Инициализация путем инициализации списка. Что касается второго, вам нужно внимательнее взглянуть на то, что я написал (что касается инициализации внутри класса ) и / или грамматики C ++ (например, член-декларатор , который ссылается на фигурную скобку или равный инициализатор ).
Люк Дантон
Хм, вы правы - я раньше тестировал GCC 4.5, что, казалось, подтвердило мои слова, но GCC 4.6 с вами согласен. И я упустил тот факт, что вы говорили об инициализации в классе. Мои извенения.
Interjay 02