Почему инициализация списка (с использованием фигурных скобок) лучше, чем альтернативы?

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

Почему?

Я не мог найти ответ на SO, поэтому позвольте мне ответить на мой собственный вопрос.

Ответ 1

В основном, копирование и вставка из Бьярна Страуструпа "Язык программирования C++, 4-е издание":

Инициализация списка не допускает сужения (§iso.8.5.4). То есть:

  • Целое число не может быть преобразовано в другое целое число, которое не может содержать его значение. Например, символ Допускается 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

Заключение

Предпочитайте {} инициализацию альтернативам, если у вас нет веских причин не делать этого.

Ответ 2

Уже есть отличные ответы о преимуществах использования инициализации списка, однако мое личное правило - НЕ использовать фигурные скобки всякий раз, когда это возможно, а вместо этого ставить его в зависимость от концептуального значения:

  • Если объект, который я создаю, концептуально содержит значения, которые я передаю в конструкторе (например, контейнеры, структуры POD, атомарные элементы, интеллектуальные указатели и т.д.), Тогда я использую фигурные скобки.
  • Если конструктор напоминает обычный вызов функции (он выполняет более или менее сложные операции, параметризованные аргументами), тогда я использую обычный синтаксис вызова функции.
  • Для инициализации по умолчанию я всегда использую фигурные скобки.
    Во-первых, таким образом, я всегда уверен, что объект инициализируется независимо от того, например, является ли он объектом. является "реальным" классом с конструктором по умолчанию, который будет вызван в любом случае, или встроенным типом /POD. Второе - в большинстве случаев - соответствует первому правилу, поскольку инициализированный объект по умолчанию часто представляет собой "пустой" объект.

По моему опыту, этот набор правил может применяться гораздо более последовательно, чем использование фигурных скобок по умолчанию, но при этом необходимо явно помнить все исключения, когда они не могут быть использованы или имеют другое значение, чем "нормальный" синтаксис вызова функции с круглыми скобками. (вызывает другую перегрузку).

Это, например, прекрасно подходит для стандартных библиотечных типов, таких как std::vector:

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

vector<int> b(10,20);   //Parenthesis -> 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

Ответ 3

Есть много причин для использования инициализации скобок, но вы должны знать, что конструктор initializer_list<> предпочтителен для других конструкторов, причем исключение является конструктором по умолчанию. Это приводит к проблемам с конструкторами и шаблонами, где конструктор типа T может быть либо списком инициализаторов, либо простым старым ctor.

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!!!
}

Предполагая, что вы не сталкиваетесь с такими классами, нет оснований не использовать список инициализаторов.

Ответ 4

Это только безопаснее, если вы не строите с -не сужением, как, скажем, Google делает в Chromium. Если вы это сделаете, то это менее безопасно.

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

Эти две комбинации означают, что они безопаснее, если то, что находится внутри, является примитивными константами, но менее безопасны, если они являются объектами.