it-swarm.com.de

Warum ist die Listeninitialisierung (mit geschweiften Klammern) besser als die Alternativen?

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

Warum?

Ich konnte auf SO keine Antwort finden, also lassen Sie mich meine eigene Frage beantworten.

326
Oleksiy

Kopieren und Einfügen von Bjarne Stroustrups "The C++ Programming Language 4th Edition" :

Listeninitialisierung erlaubt keine Verengung (§iso.8.5.4). Das ist:

  • Eine Ganzzahl kann nicht in eine andere Ganzzahl konvertiert werden, die ihren Wert nicht enthalten kann. Zum Beispiel ist char to int erlaubt, aber nicht int to char.
  • Ein Gleitkommawert kann nicht in einen anderen Gleitkommatyp konvertiert werden, der seinen Wert nicht halten kann. Zum Beispiel ist float to double erlaubt, aber nicht double to float.
  • Ein Gleitkommawert kann nicht in einen Integer-Typ konvertiert werden.
  • Ein ganzzahliger Wert kann nicht in einen Gleitkommatyp konvertiert werden.

Beispiel:

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)

}

Die einzige Situation, in der = gegenüber {} bevorzugt wird, ist die Verwendung des Schlüsselworts auto, um den vom Initialisierer bestimmten Typ zu ermitteln.

Beispiel:

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

Fazit

Ziehen Sie die {} Initialisierung Alternativen vor, es sei denn, Sie haben einen wichtigen Grund, dies nicht zu tun.

294
Oleksiy

Es gibt VIELE Gründe für die Verwendung der Klammerinitialisierung, aber Sie sollten beachten, dass das initializer_list<> Konstruktor ist den anderen Konstruktoren vorzuziehen, mit Ausnahme des Standardkonstruktors. Dies führt zu Problemen mit Konstruktoren und Vorlagen, bei denen der Konstruktor vom Typ T entweder eine Initialisierungsliste oder ein einfacher alter ctor sein kann.

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

Angenommen, Sie stoßen nicht auf solche Klassen, gibt es wenig Grund, die Liste der Initialisierer nicht zu verwenden.

85
Red XIII

Es gibt bereits gute Antworten auf die Vorteile der Listeninitialisierung. Meine persönliche Faustregel lautet jedoch, KEINE geschweiften Klammern zu verwenden, wenn dies möglich ist, sondern diese von der konzeptuellen Bedeutung abhängig zu machen:

  • Wenn das Objekt, das ich konzeptionell erstelle, die im Konstruktor übergebenen Werte enthält (z. B. Container, POD-Strukturen, Atomics, Smart Pointer usw.), verwende ich die geschweiften Klammern.
  • Wenn der Konstruktor einem normalen Funktionsaufruf ähnelt (er führt einige mehr oder weniger komplexe Operationen aus, die durch die Argumente parametrisiert werden), verwende ich die normale Funktionsaufrufsyntax.
  • Für die Standardinitialisierung verwende ich immer geschweifte Klammern.
    Zum einen bin ich mir auf diese Weise immer sicher, dass das Objekt initialisiert wird, unabhängig davon, ob es z. ist eine "echte" Klasse mit einem Standardkonstruktor, der sowieso aufgerufen wird, oder einem eingebauten/POD-Typ. Zweitens stimmt es in den meisten Fällen mit der ersten Regel überein, da ein standardmäßig initialisiertes Objekt häufig ein "leeres" Objekt darstellt.

Meiner Erfahrung nach kann dieser Regelsatz viel konsistenter angewendet werden als standardmäßig geschweifte Klammern, muss sich jedoch explizit alle Ausnahmen merken, wenn sie nicht verwendet werden können oder eine andere Bedeutung haben als die "normale" Funktionsaufrufsyntax mit Klammern (nennt eine andere Überladung).

Es ist z.B. passt gut zu Standardbibliothekstypen wie 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
83
MikeMB