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