613 Stimmen

Was sind die Vorteile der Listeninitialisierung (Verwendung von geschweiften Klammern)?

MyClass a1 {a};     // klarer und weniger fehleranfällig als die anderen drei
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

Warum?

534voto

Oleksiy Punkte 33680

Im Grunde genommen kopiere und füge ich aus Bjarne Stroustrups "The C++ Programming Language 4th Edition" ein:

Listeninitialisierung erlaubt kein Eingrenzen (§iso.8.5.4). Das bedeutet:

  • Ein Integer kann nicht in einen anderen Integer umgewandelt werden, der seinen Wert nicht halten kann. Zum Beispiel ist char zu int erlaubt, aber nicht int zu char.
  • Ein Gleitkommawert kann nicht in einen anderen Gleitkommatyp umgewandelt werden, der seinen Wert nicht halten kann. Zum Beispiel ist float zu double erlaubt, aber nicht double zu float.
  • Ein Gleitkommawert kann nicht in einen Integertyp umgewandelt werden.
  • Ein Integerwert kann nicht in einen Gleitkomma-Typ umgewandelt werden.

Beispiel:

void fun(double val, int val2) {

    int x2 = val;    // wenn val == 7.9, wird x2 zu 7 (schlecht)

    char c2 = val2;  // wenn val2 == 1025, wird c2 zu 1 (schlecht)

    int x3 {val};    // Fehler: mögliche Abschneidung (gut)

    char c3 {val2};  // Fehler: mögliche Eingrenzung (gut)

    char c4 {24};    // OK: 24 kann genau als char dargestellt werden (gut)

    char c5 {264};   // Fehler (bei 8-Bit-Zeichen angenommen): 264 kann nicht als char dargestellt werden (gut)

    int x4 {2.0};    // Fehler: keine Umwandlung vom Double- zum Int-Wert (gut)

}

Die einzige Situation, in der = gegenüber {} bevorzugt wird, ist bei der Verwendung des auto-Schlüsselworts, um den Typ durch den Initialisierer bestimmen zu lassen.

Beispiel:

auto z1 {99};   // z1 ist ein int
auto z2 = {99}; // z2 ist std::initializer_list
auto z3 = 99;   // z3 ist ein int

Schlussfolgerung

Vorzugsweise {}-Initialisierung gegenüber Alternativen verwenden, es sei denn, es gibt einen guten Grund dagegen.

186voto

MikeMB Punkte 18817

Es gibt bereits großartige Antworten zu den Vorteilen der Verwendung der Listeninitialisierung, aber meine persönliche Faustregel lautet, KEINE geschweiften Klammern zu verwenden, wann immer möglich, sondern dies vom konzeptuellen Sinn abhängig zu machen:

  • Wenn das Objekt, das ich erstelle, konzeptuell die Werte enthält, die ich dem Konstruktor übergebe (z. B. Container, POD-Strukturen, Atomare, Smart-Zeiger usw.), dann 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 sind), verwende ich die normale Funktionsaufruf-Syntax.
  • Für die Standardinitialisierung verwende ich immer geschweifte Klammern.
    Einerseits bin ich so sicher, dass das Objekt initialisiert wird, unabhängig davon, ob es sich z. B. um eine "echte" Klasse mit einem Standardkonstruktor handelt, der so oder so aufgerufen würde, oder um einen eingebauten / POD-Typ. Zweitens ist es - in den meisten Fällen - konsistent mit der ersten Regel, da ein standardmäßig initialisiertes Objekt oft ein "leeres" Objekt darstellt.

Nach meiner Erfahrung kann dieser Regelssatz viel konsistenter angewendet werden als die Verwendung von geschweiften Klammern als Standard, aber man muss sich explizit an alle Ausnahmen erinnern, wenn sie nicht verwendet werden können oder eine andere Bedeutung haben als die "normale" Funktionsaufruf-Syntax mit Klammern (ruft eine andere Überladung auf).

Es passt z. B. gut zu Standardbibliothekstypen wie std::vector:

vector a{10, 20};   // Geschweifte Klammern -> füllt den Vektor mit den Argumenten

vector b(10, 20);   // Klammern -> verwendet Argumente zur Parametrisierung einer Funktionalität,
vector c(it1, it2); // wie das Füllen des Vektors mit 10 Ganzzahlen oder das Kopieren eines Bereichs.

vector d{};      // leere Klammern -> standardmäßiger Konstruktoraufruf für Vektor, der äquivalent ist
                      // zu einem Vektor, der mit Null-Elementen gefüllt ist

120voto

Red XIII Punkte 5465

Es gibt VIELE Gründe, die geschweifte Initialisierung zu verwenden, aber Sie sollten sich dessen bewusst sein, dass der initializer_list<>-Konstruktor den anderen Konstruktoren vorgezogen wird, mit Ausnahme des Standardkonstruktors. Dies führt zu Problemen mit Konstruktoren und Vorlagen, bei denen der Typ T-Konstruktor entweder eine Initialisierungsliste oder ein einfacher alter Konstruktor sein kann.

struct Foo {
    Foo() {}

    Foo(std::initializer_list) {
        std::cout << "Initialisierungsliste" << std::endl;
    }

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

int main() {
    Foo a;
    Foo b(a); // Kopierkonstruktor
    Foo c{a}; // Kopierkonstruktor (Initialisierungslisten-Element) + Initialisierungsliste!!!
}

Vorausgesetzt, Sie stoßen nicht auf solche Klassen, gibt es wenig Grund, die Initialisierungsliste nicht zu verwenden.

9voto

codeling Punkte 10632

Aktualisierung (2022-02-11): Bitte beachten Sie, dass es zu dem Thema neuere Meinungen gibt, die gegen die Präferenz des {}-Initialisierers argumentieren, wie zum Beispiel Arthur Dwyer in seinem Blog-Beitrag zu The Knightmare of Initialization in C++.

Ursprüngliche Antwort:

Lesen Sie Herb Sutters (aktualisierten) GotW #1. Dort wird ausführlich der Unterschied zwischen diesen erklärt, sowie einige weitere Optionen, zusammen mit einigen Fallstricken, die relevant sind, um das Verhalten der verschiedenen Optionen zu unterscheiden.

Der Kernpunkt / aus Abschnitt 4 kopiert:

Wann sollten Sie ( ) vs. { } Syntax verwenden, um Objekte zu initialisieren? Warum? Hier ist die einfache Richtlinie:

Richtlinie: Verwenden Sie bevorzugt die Initialisierung mit { }, wie zum Beispiel vector v = { 1, 2, 3, 4 }; oder auto v = vector{ 1, 2, 3, 4 };, da es konsistenter, korrekter ist und vermeidet, alte Fallstricke zu kennen. In Fällen mit einem Argument, in denen Sie nur das =-Zeichen sehen möchten, wie int i = 42; und auto x = anything; ist das Weglassen der Klammern in Ordnung. …

Das deckt die überwiegende Mehrheit der Fälle ab. Es gibt nur eine wichtige Ausnahme:

… In seltenen Fällen, wie vector v(10,20); oder auto v = vector(10,20);, verwenden Sie die Initialisierung mit ( ), um explizit einen Konstruktor aufzurufen, der sonst durch einen initializer_list-Konstruktor versteckt ist.

Dennoch sollte dies im Allgemeinen "selten" sein, da Standard- und Kopierkonstruktion bereits speziell sind und gut mit { } funktionieren, und gutes Klassendesign jetzt den Resort-zu-( )-Fall für benutzerdefinierte Konstruktoren weitgehend vermeidet, aufgrund dieser abschließenden Designrichtlinie:

Richtlinie: Vermeiden Sie es, in einer Klasse einen Konstruktor bereitzustellen, der mehrdeutig mit einem initializer_list-Konstruktor überladen ist, damit Benutzer nicht ( ) verwenden müssen, um einen solchen versteckten Konstruktor zu erreichen.

Beachten Sie auch die Core-Richtlinien zu diesem Thema: ES.23: Bevorzugen Sie die {}-Initialisierungssyntax.

6voto

Allan Jensen Punkte 444

Nur sicherer, solange Sie nicht mit -Wno-narrowing erstellen, wie zum Beispiel Google es in Chromium macht. Wenn Sie das tun, ist es weniger sicher. Ohne diese Flagge werden die einzigen unsicheren Fälle jedoch durch C++20 behoben.

Hinweis:

  1. Geschweifte Klammern sind sicherer, weil sie keine Verengung zulassen.
  2. Geschweifte Klammern sind weniger sicher, weil sie private oder gelöschte Konstruktoren umgehen und explizit markierte Konstruktoren implizit aufrufen können.

Die Kombination dieser beiden bedeutet, dass sie sicherer sind, wenn primitive Konstanten enthalten sind, aber weniger sicher, wenn es sich um Objekte handelt (wird jedoch in C++20 behoben).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X