Чому ініціалізація списку (за допомогою фігурних дужок) краща за альтернативи?


405
MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

Чому?

Я не зміг знайти відповідь на ТА, тому дозвольте мені відповісти на власне запитання.


12
Чому б не використовувати auto?
Марк Гарсія

33
Це правда, це зручно, але це зменшує читабельність на мій погляд - мені подобається бачити, який тип об’єкта, читаючи код. Якщо ви на 100% впевнені, що таке тип об’єкта, навіщо використовувати авто? І якщо ви використовуєте ініціалізацію списку (читайте мою відповідь), ви можете бути впевнені, що це завжди правильно.
Олексій

102
@Oleksiy: std::map<std::string, std::vector<std::string>>::const_iteratorхотів би слова з тобою.
Xeo

9
@Oleksiy Я рекомендую прочитати цей GotW .
Раппц

17
@doc Я б сказав, що using MyContainer = std::map<std::string, std::vector<std::string>>;це навіть краще (тим більше, що ви можете надати шаблони!)
JAB

Відповіді:


356

В основному копіювання та вставлення з "4-ї редакції мови програмування" Bjarne Stroustrup :

Ініціалізація списку не дозволяє звужуватися (§iso.8.5.4). Це є:

  • Ціле число не може бути перетворене в інше ціле число, яке не може утримувати його значення. Наприклад, char до int дозволено, але не int to char.
  • Значення з плаваючою комою не може бути перетворене в інший тип з плаваючою комою, який не може утримувати своє значення. Наприклад, дозволено плавати в два рази, але не плавати вдвічі.
  • Значення з плаваючою комою не можна перетворити на цілий тип.
  • Ціле значення не можна перетворити на тип з плаваючою комою.

Приклад:

void fun(double val, int val2) {

    int x2 = val; // if val==7.9, x2 becomes 7 (bad)

    char c2 = val2; // if val2==1025, c2 becomes 1 (bad)

    int x3 {val}; // error: possible truncation (good)

    char c3 {val2}; // error: possible narrowing (good)

    char c4 {24}; // OK: 24 can be represented exactly as a char (good)

    char c5 {264}; // error (assuming 8-bit chars): 264 cannot be 
                   // represented as a char (good)

    int x4 {2.0}; // error: no double to int value conversion (good)

}

Тільки ситуація , коли = краще , ніж {}, коли з допомогою autoключового слова , щоб отримати тип , який визначається ініціалізатор.

Приклад:

auto z1 {99};   // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

Висновок

Віддайте перевагу {} ініціалізації над альтернативами, якщо у вас немає вагомих причин цього не робити.


52
Існує також той факт, що використання ()може бути проаналізовано як декларація функції. Можна заплутати і непослідовно сказати, T t(x,y,z);але ні T t(). І іноді, певні xви, навіть не можете сказати T t(x);.
juanchopanza

84
Я категорично не згоден з цією відповіддю; braced ініціалізація стає повним безладом, коли у вас є типи, у яких ctor приймає a std::initializer_list. RedXIII згадує про це питання (і просто усуває його), тоді як ви повністю його ігноруєте. A(5,4)і A{5,4}може викликати абсолютно різні функції, і це важливо знати. Це навіть може призвести до дзвінків, які здаються неінтуїтивними. Казання, що вам слід віддати перевагу {}за замовчуванням, призведе до того, що люди нерозуміють, що відбувається. Але це не ваша вина. Я особисто думаю, що це надзвичайно погано продумана функція.
користувач1520427

13
@ user1520427 Ось чому є ", якщо у вас немає вагомих причин не ".
Олексій

67
Хоча це питання давнє, він має досить багато хітів, тому я додаю це лише для довідки (я його ще не бачив ніде на сторінці). З C ++ 14 з новими Правилами автоматичного вирахування з списку, поставленого braced-init , тепер можна писати, auto var{ 5 }і він буде виведений як intне більше std::initializer_list<int>.
Едоардо Спаркон Домінічі

12
Ха-ха, з усіх коментарів досі не зрозуміло, що робити. Ясно, що специфікація C ++ - це безлад!
БарабанM

113

Про переваги використання ініціалізації списку вже є чудові відповіді, однак моє особисте правило - НЕ використовувати фігурні дужки, коли це можливо, а замість цього зробити залежним від понятійного значення:

  • Якщо об'єкт, який я створюю концептуально, містить значення, які я передаю в конструкторі (наприклад, контейнери, структури POD, атома, розумні покажчики тощо), то я використовую дужки.
  • Якщо конструктор нагадує звичайний виклик функції (він виконує деякі більш-менш складні операції, параметризовані аргументами), то я використовую синтаксис виклику звичайної функції.
  • Для ініціалізації за замовчуванням я завжди використовую фігурні дужки.
    З одного боку, таким чином я завжди впевнений, що об'єкт ініціалізується незалежно від того, наприклад, це "реальний" клас із конструктором за замовчуванням, який би називався так чи інакше або вбудованим / POD-типом. По-друге, це - у більшості випадків - відповідає першому правилу, оскільки ініціалізований за замовчуванням об'єкт часто представляє "порожній" об'єкт.

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

Наприклад, вона добре поєднується зі стандартними типами бібліотек, як std::vector:

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments

vector<int> b(10,20);   //Parentheses -> uses arguments to parametrize some functionality,                          
vector<int> c(it1,it2); //like filling the vector with 10 integers or copying a range.

vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                      //to a vector that is filled with zero elements

11
Повністю згоден з більшістю вашої відповіді. Однак ви не вважаєте, що розміщення порожніх дужок для вектора просто зайве? Я маю на увазі, це нормально, коли вам потрібно ініціалізувати значення об'єкта загального типу T, але яка мета цього робити для негенерального коду?
Михайло

8
@Mikhail: Це, безумовно, зайве, але моя звичка завжди робити ініціалізацію локальної змінної явною. Як я писав, в основному мова йде про консистенцію, тому я не забуваю про це, коли це має значення. Це, звичайно, нічого, що я б згадував у огляді коду чи вклав у керівництво зі стилем.
MikeMB

4
досить чистий набір правил.
laike9m

5
Це, безумовно, найкраща відповідь. {} - це як спадщина - легко зловживати, що призводить до важкого для розуміння коду.
UKMonkey

2
Приклад @MikeMB: const int &b{}<- не намагається створити неініціалізовану посилання, але прив'язує її до тимчасового цілого об'єкта. Другий приклад: struct A { const int &b; A():b{} {} };<- не намагається створити неініціалізовану посилання (як це ()було б), але прив’яжіть її до тимчасового цілого об'єкта, а потім залиште його висячим. GCC навіть з -Wallне попереджає для другого прикладу.
Йоханнес Шауб - ліб

91

Є багато причин для ініціалізації використання розпірки, але ви повинні знати , що конструктор найкращим за всі інші конструктори , виняток становить за замовчуванням, конструктор. Це призводить до проблем з конструкторами та шаблонами, де конструктор типу може бути списком ініціалізатора або простим старим ctor.initializer_list<>T

struct Foo {
    Foo() {}

    Foo(std::initializer_list<Foo>) {
        std::cout << "initializer list" << std::endl;
    }

    Foo(const Foo&) {
        std::cout << "copy ctor" << std::endl;
    }
};

int main() {
    Foo a;
    Foo b(a); // copy ctor
    Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}

Якщо припустити, що ви не стикаєтеся з такими класами, є мало причин не використовувати список інсталайзера.


20
Це дуже важливий момент у загальному програмуванні. Коли ви пишете шаблони, не використовуйте braced-init-list (назва стандарту для { ... }), якщо ви не хочете initializer_listсемантики (ну, а може, і для об'єкта, що конструює за замовчуванням).
Xeo

82
Я, чесно кажучи, не розумію, чому std::initializer_listправило взагалі існує - це просто додає плутанині і безладу мові. Що поганого в тому, Foo{{a}}якщо робити std::initializer_listконструктор? Це здається набагато простіше зрозуміти, ніж мати std::initializer_listперевагу над усіма іншими перевантаженнями.
користувач1520427

5
+1 за вищезазначений коментар, тому що це дійсно безлад я думаю !! це не логіка; Foo{{a}}Дотримується певної логіки для мене набагато більше, ніж Foo{a}це перетворюється на пріоритет списку intializer (ДБЖ, можливо, користувач думає хм ...)
Габріель

32
В основному C ++ 11 замінює один безлад іншим безладом. О, вибачте, що це не замінює - додає до цього. Як ви можете знати, якщо ви не стикаєтеся з такими заняттями? Що робити, якщо ви починаєте без std::initializer_list<Foo> конструктора, але він буде доданий до Fooкласу в якийсь момент, щоб розширити його інтерфейс? Тоді користувачі Fooкласу накручуються.
док

10
.. які "МНОГО причин використовувати ініціалізацію дужок"? Ця відповідь вказує на одну причину ( initializer_list<>), яку насправді не кваліфікує той, хто каже, що вона є кращою, а потім переходить до згадки про один хороший випадок, коли це НЕ є кращим. Що мені не вистачає, щоб ~ 30 інших людей (станом на 2016-04-21) вважали корисними?
dwanderson

0

Це безпечніше лише до тих пір, поки ви не будете створювати з-за-звуження, як, наприклад, Google у Chromium. Якщо ви це зробите, то МЕНШЕ безпечно. Без цього прапора лише C ++ 20 буде виправлено лише небезпечні випадки.

Примітка: А) фігурні дужки безпечніші, оскільки не допускають звуження. B) Кучеряві брекери менш безпечні, оскільки можуть обходити приватні або видалені конструктори та викликати явні позначені конструктори неявно.

Ці дві комбіновані засоби означають, що вони безпечніші, якщо всередині є примітивні константи, але менш безпечні, якщо вони є об'єктами (хоча вони зафіксовані в C ++ 20)


Я спробував обробляти на goldbolt.org обхід "явних" або "приватних" конструкторів за допомогою наданого зразкового коду та створення одного чи іншого приватного чи явного, і був нагороджений відповідною помилкою компілятора [s]. Хочете підкріпити це копією зразкового коду?
Марк Сторер

Це виправлення випуску, запропонованого для C ++ 20: open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1008r1.pdf
Allan Jensen

1
Якщо ви відредагуєте свою відповідь, щоб показати, про яку версію C ++ ви говорите, я би радий змінити свій голос.
Марк Сторер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.