Стиль кодування в кінцевому підсумку суб'єктивний, і малоймовірно, що від цього вийдуть значні переваги від продуктивності. Але ось що я б сказав, що ви отримуєте від ліберального використання рівномірної ініціалізації:
Мінімізує надлишкові назви
Розглянемо наступне:
vec3 GetValue()
{
return vec3(x, y, z);
}
Чому мені потрібно вводити vec3
два рази? Чи є в цьому сенс? Компілятор добре знає, яку функцію повертає. Чому я не можу просто сказати: "зателефонуйте конструктору того, що я повертаю з цими значеннями і поверніть його". З рівномірною ініціалізацією я можу:
vec3 GetValue()
{
return {x, y, z};
}
Все працює.
Ще краще - це для аргументів функції. Врахуйте це:
void DoSomething(const std::string &str);
DoSomething("A string.");
Це працює без необхідності вводити ім'я типу, оскільки std::string
знає, як створити себе const char*
неявно. Це чудово. Але що робити, якщо цей рядок прийшов, скажімо, RapidXML. Або струна Луа. Тобто, скажімо, я фактично знаю довжину струни спереду. std::string
Конструктор , який приймає const char*
доведеться взяти довжину рядка , якщо я просто пропускати const char*
.
Існує перевантаження, яка явно займає довжину. Але використовувати його, я повинен був би зробити це: DoSomething(std::string(strValue, strLen))
. Чому там додаткове ім’я типу? Компілятор знає, що таке тип. Як і у випадку auto
, ми можемо уникати додаткових імен:
DoSomething({strValue, strLen});
Це просто працює. Ніяких імен, без суєти, нічого. Компілятор робить свою роботу, код коротший, і всі радіють.
Зрозуміло, що можна зробити аргументи, що перша версія ( DoSomething(std::string(strValue, strLen))
) є більш розбірливою. Тобто, очевидно, що відбувається і хто що робить. Це правда, певною мірою; розуміння коду на основі рівномірної ініціалізації вимагає перегляду прототипу функції. Це та сама причина, чому деякі кажуть, що ви ніколи не повинні передавати параметри за допомогою non-const посилання: щоб ви могли бачити на сайті виклику, якщо значення змінюється.
Але те саме можна сказати auto
; знати, що ви отримуєте, auto v = GetSomething();
вимагає ознайомитися з визначенням GetSomething
. Але це не перестало auto
використовувати з майже необачним відмовою, як тільки ви отримаєте доступ до нього. Особисто я думаю, що це буде добре, коли звикнеш. Особливо з хорошим IDE.
Ніколи не отримуйте самого роздратування
Ось якийсь код.
class Bar;
void Func()
{
int foo(Bar());
}
Поп-вікторина: що таке foo
? Якщо ви відповіли "змінною", ви помиляєтесь. Це фактично прототип функції, яка приймає за свій параметр функцію, яка повертає a Bar
, а foo
значення повернення функції - int.
Це називається "Найпопулярнішим розбором" С ++, оскільки воно абсолютно не має сенсу для людини. Але правила С ++, на жаль, вимагають цього: якщо це можливо інтерпретувати як прототип функції, то воно буде . Проблема в тому Bar()
; це може бути одна з двох речей. Це може бути тип з назвою Bar
, це означає, що він створює тимчасовий характер. Або це може бути функція, яка не приймає жодних параметрів і повертає a Bar
.
Уніфікована ініціалізація не може бути інтерпретована як прототип функції:
class Bar;
void Func()
{
int foo{Bar{}};
}
Bar{}
завжди створює тимчасовий характер. int foo{...}
завжди створює змінну.
Є багато випадків, коли ви хочете використовувати, Typename()
але просто не можете через правила розбору C ++. З Typename{}
, двозначності немає.
Причини не робити
Єдина реальна сила, яку ти віддаєш, - це звуження. Не можна ініціалізувати менше значення з більшим з рівномірною ініціалізацією.
int val{5.2};
Це не складеться. Це можна зробити за допомогою старомодної ініціалізації, але не рівномірної ініціалізації.
Це було зроблено частково для того, щоб списки ініціалізаторів справді працювали. Інакше було б багато неоднозначних випадків щодо типів списків ініціалізаторів.
Звичайно, деякі можуть стверджувати, що такий код заслуговує на те, щоб не компілювати. Особисто я погоджуюся; звуження дуже небезпечно і може призвести до неприємної поведінки. Напевно, найкраще вирішити ці проблеми на початку компілятора. Принаймні, звуження говорить про те, що хтось не надто замислюється над кодом.
Зауважте, що компілятори зазвичай попереджають вас про подібні речі, якщо рівень попередження високий. Тож справді, все це робить перетворення попередження про насильницьку помилку. Дехто може сказати, що ви все одно повинні це робити;)
Є ще одна причина не:
std::vector<int> v{100};
Що це робить? Це може створити vector<int>
зі сто створених за замовчуванням елементів. Або це може створити vector<int>
з 1 предметом значення, яке є 100
. І те й інше теоретично можливо.
Насправді це робить останнє.
Чому? Списки ініціалізаторів використовують той же синтаксис, що і рівномірна ініціалізація. Тож повинні бути деякі правила, які пояснюють, що робити у разі неоднозначності. Правило досить просте: якщо компілятор може використовувати конструктор списку ініціалізатора зі списком, ініційованим дужкою, то він буде . Оскільки vector<int>
конструктор списку ініціалізатора, який займає initializer_list<int>
, і {100} може бути дійсним initializer_list<int>
, тому він повинен бути .
Для того, щоб отримати конструктор розмірів, ви повинні використовувати ()
замість {}
.
Зауважте, що якби це було vector
щось, що не перетворювалося на ціле число, цього не сталося. Ініціалізатор_list не підходить конструктору списку ініціалізаторів цього vector
типу, і тому компілятор може вільно вибирати з інших конструкторів.