Поза загальним кодом (тобто шаблонами) ви можете (і я це роблю) використовувати брекети скрізь . Одна перевага полягає в тому, що він працює скрізь, наприклад, навіть для ініціалізації в класі:
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-ініціалізацію, яка відкладає на пряму- ініціалізація, якщо і лише тоді, коли конструктор не 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
.