За пределами общего кода (например, шаблонов) вы можете (и я использую) везде использовать фигурные скобки . Одним из преимуществ является то, что он работает везде, например, даже для инициализации в классе:
struct foo {
std::string a = { "foo" };
std::string b { "bar" };
std::string c("qux");
std::string d = "baz";
};
или для аргументов функции:
void foo(std::pair<int, double*>);
foo({ 42, nullptr });
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
.