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

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

Зачем?

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

5 ответов

Решение

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

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

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

Пример:

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 initializer_list<int>
auto z2 = 99; // z2 is an int

Заключение

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

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

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

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

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

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

vector<int> b(10,20);   //Parentesis -> uses arguments to parameterize 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

Есть много причин использовать инициализацию скобок, но вы должны знать, что 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!!!
}

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

Вам необходимо прочитать (обновленный) GotW #1 Херба Саттера. Это подробно объясняет разницу между этими и еще несколькими параметрами, а также несколько ошибок, которые важны для различения поведения различных параметров.

Суть / скопировано из раздела 4:

Когда следует использовать синтаксис () или {} для инициализации объектов? Почему? Вот простой совет:

Рекомендация: предпочтительнее использовать инициализацию с {}, например вектор v = {1, 2, 3, 4}; или auto v = vector {1, 2, 3, 4} ;, потому что он более последовательный, более правильный и позволяет избежать необходимости знать о ловушках старого стиля. В случаях с одним аргументом, когда вы предпочитаете видеть только знак =, например int i = 42; и auto x = что угодно; отсутствие фигурных скобок - это нормально. …

Это охватывает подавляющее большинство случаев. Есть только одно главное исключение:

… В редких случаях, например вектор v (10,20); или auto v = vector(10,20); используйте инициализацию с помощью (), чтобы явно вызвать конструктор, который в противном случае скрыт конструктором initializer_list.

Однако причина, по которой это, как правило, должно быть «редким», заключается в том, что конструкция по умолчанию и копирование уже являются специальными и отлично работают с {}, а хороший дизайн класса теперь в основном позволяет избежать случая обращения к - () для определяемых пользователем конструкторов из-за этого. окончательное руководство по дизайну:

Рекомендация: при разработке класса избегайте предоставления конструктора, который неоднозначно перегружается конструктором initializer_list, чтобы пользователям не приходилось использовать () для доступа к такому скрытому конструктору.

Это безопаснее, если вы не строите с -Wno-сужением, как, скажем, Google в Chromium. Если да, то это НЕ БЕЗОПАСНО. Однако без этого флага единственные небезопасные случаи будут исправлены C++20.

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

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

Другие вопросы по тегам