Когда использовать инициализатор в скобках? - PullRequest
89 голосов
/ 02 апреля 2012

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

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

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

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

Ответы [ 3 ]

60 голосов
/ 02 апреля 2012

I думаю следующие рекомендации могут быть хорошими:

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

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

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

25 голосов
/ 02 апреля 2012

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

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

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

16 голосов
/ 02 апреля 2012

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

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 в терминах прямой инициализации, тогда как мыиспользование brace-initialization, которое отсылает к прямой инициализации тогда и только тогда, когда не имеет конструктора, принимающего 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 way 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...