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

Lesezeit: 6 Minuten

Warum ist die Listeninitialisierung mit geschweiften Klammern besser als die
Oleksiy

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

Warum?

  • Würde nicht ins Kommentarfeld passen ;). Wie auch immer, um aus dem verlinkten Artikel zu zitieren: “… die Hauptgründe für die Deklaration von Variablen mit auto sind Korrektheit, Leistung, Wartbarkeit und Robustheit – und, ja, Bequemlichkeit …”.

    – Markus García

    14. August 2013 um 4:01 Uhr

  • Das stimmt, es ist praktisch, aber es verringert meiner Meinung nach die Lesbarkeit – ich mag es sehen welcher Typ ein Objekt beim Lesen von Code ist. Wenn Sie sich zu 100 % sicher sind, um welchen Objekttyp es sich handelt, warum sollten Sie dann auto verwenden? Und wenn Sie die Listeninitialisierung verwenden (lesen Sie meine Antwort), können Sie sicher sein, dass sie immer korrekt ist.

    – Oleksi

    14. August 2013 um 4:04 Uhr

  • @Oleksij: std::map<std::string, std::vector<std::string>>::const_iterator würde gerne mit dir sprechen.

    – Xeo

    14. August 2013 um 6:03 Uhr

  • @Oleksiy Ich empfehle zu lesen dieses GotW.

    – Raptz

    14. August 2013 um 6:05 Uhr

  • @doc würde ich sagen using MyContainer = std::map<std::string, std::vector<std::string>>; ist sogar noch besser (vor allem, da Sie es schablonen können!)

    – JAB

    18. Februar 2016 um 22:26 Uhr


Warum ist die Listeninitialisierung mit geschweiften Klammern besser als die
Oleksiy

Grundsätzlich Kopieren und Einfügen von Bjarne Stroustrup “Die Programmiersprache C++ 4. Auflage”:

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

  • Eine Ganzzahl kann nicht in eine andere Ganzzahl konvertiert werden, die ihren Wert nicht halten kann. Beispielsweise 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. Float-to-Double ist beispielsweise erlaubt, aber nicht double-to-float.
  • Ein Gleitkommawert kann nicht in einen ganzzahligen Typ konvertiert werden.
  • Ein ganzzahliger Wert kann nicht in einen Fließkommatyp 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 nur Situation, in der = gegenüber {} bevorzugt wird, wenn verwendet wird auto Schlüsselwort, um den vom Initialisierer bestimmten Typ abzurufen.

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

Bevorzugen Sie die {}-Initialisierung gegenüber Alternativen, es sei denn, Sie haben einen triftigen Grund, dies nicht zu tun.

  • Hinzu kommt die Tatsache, dass mit () kann als Funktionsdeklaration geparst werden. Es ist verwirrend und widersprüchlich, was Sie sagen können T t(x,y,z); aber nicht T t(). Und manchmal bist du dir sicher xkann man gar nicht sagen T t(x);.

    – Juanchopanza

    14. August 2013 um 5:23 Uhr

  • Ich bin mit dieser Antwort absolut nicht einverstanden; Die geklammerte Initialisierung wird zu einem kompletten Durcheinander, wenn Sie Typen mit einem ctor haben, der a akzeptiert std::initializer_list. RedXIII erwähnt dieses Problem (und wischt es einfach weg), während Sie es vollständig ignorieren. A(5,4) und A{5,4} kann ganz andere Funktionen aufrufen, und das ist wichtig zu wissen. Es kann sogar zu Anrufen führen, die nicht intuitiv erscheinen. Zu sagen, dass Sie es vorziehen sollten {} standardmäßig dazu führen, dass die Leute missverstehen, was vor sich geht. Das ist aber nicht deine Schuld. Ich persönlich halte das für ein extrem schlecht durchdachtes Feature.

    – Benutzer1520427

    2. Februar 2015 um 1:40 Uhr

  • @user1520427 Deshalb gibt es den “es sei denn, Sie haben einen triftigen Grund, dies nicht zu tun” Teil.

    – Oleksi

    2. Februar 2015 um 16:54 Uhr

  • Obwohl diese Frage alt ist, hat sie einige Treffer, daher füge ich sie hier nur als Referenz hinzu (ich habe sie nirgendwo anders auf der Seite gesehen). Ab C++14 mit dem neuen Regeln für den automatischen Abzug von geklammerten Init-Listen es ist jetzt möglich zu schreiben auto var{ 5 } und es wird abgeleitet als int nicht mehr als std::initializer_list<int>.

    – Edoardo Dominici

    31. Oktober 2015 um 12:26 Uhr


  • Haha, aus all den Kommentaren ist immer noch nicht klar, was zu tun ist. Klar ist, dass die C++-Spezifikation ein Durcheinander ist!

    – Trommel

    12. März 2019 um 13:19 Uhr

1647207613 299 Warum ist die Listeninitialisierung mit geschweiften Klammern besser als die
Mike MB

Es gibt bereits tolle Antworten zu den Vorteilen der Listeninitialisierung, aber meine persönliche Faustregel ist, möglichst keine geschweiften Klammern zu verwenden, sondern es von der konzeptionellen Bedeutung abhängig zu machen:

  • Wenn das Objekt, das ich erstelle, konzeptionell die Werte enthält, die ich im Konstruktor übergebe (z. B. Container, POD-Strukturen, Atomic, intelligente Zeiger 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 so immer sicher, dass das Objekt initialisiert wird, egal ob es sich zB um eine “echte” Klasse mit einem Default-Konstruktor handelt, der sowieso 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.

Meiner Erfahrung nach kann dieser Regelsatz viel konsistenter angewendet werden, als standardmäßig geschweifte Klammern zu verwenden, sich aber alle Ausnahmen explizit merken zu müssen, wenn sie nicht verwendet werden können oder eine andere Bedeutung haben als die “normale” Funktionsaufrufsyntax mit Klammern (ruft eine andere Überladung auf).

Es passt zB gut zu Standard-Bibliothekstypen wie 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

  • Stimme den meisten Ihrer Antworten voll und ganz zu. Denken Sie jedoch nicht, dass es einfach überflüssig ist, leere Klammern für Vektoren zu setzen? Ich meine, es ist in Ordnung, wenn Sie ein Objekt des generischen Typs T initialisieren müssen, aber was ist der Zweck, dies für nicht generischen Code zu tun?

    – Michail

    27. November 2016 um 11:15 Uhr


  • @Mikhail: Es ist sicherlich überflüssig, aber es ist eine Angewohnheit von mir, die Initialisierung lokaler Variablen immer explizit zu machen. Wie ich geschrieben habe, geht es hier hauptsächlich um Konsistenz, also vergesse ich es nicht, wenn es darauf ankommt. Es ist jedoch sicherlich nichts, was ich in einem Code-Review erwähnen oder in einen Styleguide aufnehmen würde.

    – MikeMB

    27. November 2016 um 16:10 Uhr

  • ziemlich sauberer Regelsatz.

    – See9m

    20. März 2018 um 5:57 Uhr

  • Dies ist bei weitem die beste Antwort. {} ist wie Vererbung – leicht zu missbrauchen, was zu schwer verständlichem Code führt.

    – UKMonkey

    9. Mai 2018 um 13:36 Uhr


  • @MikeMB Beispiel: const int &b{} <- versucht nicht, eine nicht initialisierte Referenz zu erstellen, sondern bindet sie an ein temporäres Integer-Objekt. Zweites Beispiel: struct A { const int &b; A():b{} {} }; <- versucht nicht, eine nicht initialisierte Referenz (wie () tun würde), aber binden Sie es an ein temporäres Integer-Objekt und lassen Sie es dann hängen. GCC sogar mit -Wall warnt nicht vor dem zweiten Beispiel.

    – Johannes Schaub – litb

    18. November 2018 um 18:05 Uhr


1647207614 870 Warum ist die Listeninitialisierung mit geschweiften Klammern besser als die
Rot XIII

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

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 begegnen solchen Klassen nicht, gibt es wenig Grund, die Initialisiererliste nicht zu verwenden.

  • Das ist ein sehr wichtiger Punkt in der generischen Programmierung. Wenn Sie Vorlagen schreiben, nicht Verwenden Sie geklammerte Init-Listen (der Name des Standards für { ... }), es sei denn, Sie möchten die initializer_list Semantik (na ja, und vielleicht für die Standardkonstruktion eines Objekts).

    – Xeo

    14. August 2013 um 7:11 Uhr


  • Ich verstehe ehrlich gesagt nicht, warum die std::initializer_list Es gibt sogar eine Regel – sie fügt der Sprache nur Verwirrung und Chaos hinzu. Was ist falsch daran zu tun Foo{{a}} wenn du willst std::initializer_list Konstrukteur? Das scheint so viel einfacher zu verstehen als zu haben std::initializer_list haben Vorrang vor allen anderen Überladungen.

    – Benutzer1520427

    2. Februar 2015 um 1:48 Uhr

  • +1 für den obigen Kommentar, weil es meiner Meinung nach ein wirkliches Durcheinander ist !! es ist nicht logisch; Foo{{a}} Einer Logik folgt für mich weit mehr als Foo{a}was zur Priorität der Initialisierungsliste wird (Ups, könnte der Benutzer denken, hm …)

    – Gabriel

    19. April 2015 um 19:21 Uhr

  • Grundsätzlich ersetzt C++11 ein Durcheinander durch ein anderes Durcheinander. Oh, tut mir leid, es ersetzt es nicht – es fügt es hinzu. Wie können Sie wissen, ob Sie solchen Klassen nicht begegnen? Was ist, wenn Sie anfangen ohne std::initializer_list<Foo> Konstruktor, aber es wird sein hinzugefügt zum Foo Klasse irgendwann ihre Schnittstelle erweitern? Dann Benutzer von Foo Klasse sind durcheinander.

    – mip

    29. Mai 2015 um 9:02 Uhr


  • .. was sind die “VIELEN Gründe, Klammerinitialisierung zu verwenden”? Diese Antwort weist auf einen Grund hin (initializer_list<>), was es nicht wirklich qualifiziert WHO sagt, dass es bevorzugt wird, und fährt dann fort, einen guten Fall zu erwähnen, wo es ist NICHT bevorzugt. Was vermisse ich, das ~30 andere Personen (Stand: 21.04.2016) hilfreich fanden?

    – Dwanderson

    21. April 2016 um 14:19 Uhr

1647207614 162 Warum ist die Listeninitialisierung mit geschweiften Klammern besser als die
Allan Jensen

Es ist nur sicherer, solange Sie nicht mit -Wno-Narrowing bauen, wie es beispielsweise Google in Chromium tut. Wenn Sie dies tun, ist es WENIGER sicher. Ohne dieses Flag werden die einzigen unsicheren Fälle jedoch von C++20 behoben.

Hinweis: A) Geschweifte Klammern sind sicherer, da sie keine Verengung zulassen. B) Geschweifte Klammern sind weniger sicher, da sie private oder gelöschte Konstruktoren umgehen und explizit markierte Konstruktoren implizit aufrufen können.

Diese beiden Kombinationen bedeuten, dass sie sicherer sind, wenn es sich bei dem Inhalt um primitive Konstanten handelt, aber weniger sicher, wenn es sich um Objekte handelt (obwohl in C++20 behoben).

Warum ist die Listeninitialisierung mit geschweiften Klammern besser als die
Kodierung

Update (11.02.2022): Beachten Sie, dass es zu diesem Thema neuere Meinungen als die ursprünglich gepostete (unten) gibt, die gegen die Bevorzugung des {}-Initialisierers argumentieren, wie Arthur Dwyer in seinem Blogbeitrag on Der Knightmare der Initialisierung in C++.

Ursprüngliche Antwort:

Lesen Herb Sutters (aktualisiert) GotW #1. Dies erklärt im Detail den Unterschied zwischen diesen und einigen weiteren Optionen, zusammen mit einigen Fallstricken, die für die Unterscheidung des Verhaltens der verschiedenen Optionen relevant sind.

Das Wesentliche / kopiert aus Abschnitt 4:

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

Richtlinie: Bevorzugen Sie die Initialisierung mit { }, z. B. Vektor v = { 1, 2, 3, 4 }; oder auto v = vector{ 1, 2, 3, 4 };, weil es konsistenter und korrekter ist und es vermeidet, sich überhaupt mit Fallstricken im alten Stil auskennen zu müssen. In Fällen mit einem Argument, in denen Sie lieber nur das =-Zeichen sehen möchten, wie z. B. int i = 42; und auto x = irgendetwas; das Weglassen der Klammern ist in Ordnung. …

Damit sind die meisten Fälle abgedeckt. Es gibt nur eine Hauptausnahme:

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

Der Grund, warum dies jedoch im Allgemeinen „selten“ sein sollte, liegt darin, dass Standard- und Kopierkonstruktion bereits etwas Besonderes sind und gut mit { } funktionieren, und gutes Klassendesign vermeidet aus diesem Grund jetzt meistens den Rückgriff auf ()-Fall für benutzerdefinierte Konstruktoren endgültige Designrichtlinie:

Richtlinie: Vermeiden Sie beim Entwerfen einer Klasse die Bereitstellung eines Konstruktors, der mehrdeutig mit einem initializer_list-Konstruktor überladen wird, damit Benutzer nicht ( ) verwenden müssen, um einen solchen versteckten Konstruktor zu erreichen.

Siehe auch die Kernrichtlinien zu diesem Thema: ES.23: Bevorzugen Sie die {}-Initialisierer-Syntax.

999090cookie-checkWarum ist die Listeninitialisierung (mit geschweiften Klammern) besser als die Alternativen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy