In C ++ 11 haben wir diese neue Syntax zum Initialisieren von Klassen, die uns eine Vielzahl von Möglichkeiten zum Initialisieren von Variablen bietet.
{ // Example 1
int b(1);
int a{1};
int c = 1;
int d = {1};
}
{ // Example 2
std::complex<double> b(3,4);
std::complex<double> a{3,4};
std::complex<double> c = {3,4};
auto d = std::complex<double>(3,4);
auto e = std::complex<double>{3,4};
}
{ // Example 3
std::string a(3,'x');
std::string b{3,'x'}; // oops
}
{ // Example 4
std::function<int(int,int)> a(std::plus<int>());
std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
std::unique_ptr<int> a(new int(5));
std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
std::locale::global(std::locale("")); // copied from 22.4.8.3
std::locale::global(std::locale{""});
}
{ // Example 7
std::default_random_engine a {}; // Stroustrup's FAQ
std::default_random_engine b;
}
{ // Example 8
duration<long> a = 5; // Stroustrup's FAQ too
duration<long> b(5);
duration<long> c {5};
}
Für jede Variable, die ich deklariere, muss ich mir überlegen, welche Initialisierungssyntax ich verwenden soll, und dies verlangsamt meine Codierungsgeschwindigkeit. Ich bin sicher, das war nicht die Absicht, die geschweiften Klammern einzuführen.
Wenn es um Vorlagencode geht, kann das Ändern der Syntax zu unterschiedlichen Bedeutungen führen. Daher ist es wichtig, den richtigen Weg einzuschlagen.
Ich frage mich, ob es eine universelle Richtlinie gibt, welche Syntax man wählen sollte.
c++
c++11
initializer-list
Helami
quelle
quelle
Antworten:
Ich denke, das Folgende könnte eine gute Richtlinie sein:
Wenn der (einzelne) Wert, mit dem Sie initialisieren, der genaue Wert des Objekts sein soll, verwenden Sie die Initialisierung copy (
=
) (da Sie dann im Fehlerfall niemals versehentlich einen expliziten Konstruktor aufrufen, der den angegebenen Wert im Allgemeinen interpretiert anders). Überprüfen Sie an Stellen, an denen keine Kopierinitialisierung verfügbar ist, ob die Klammerinitialisierung die richtige Semantik aufweist, und verwenden Sie diese, falls dies der Fall ist. Andernfalls verwenden Sie die Klammerinitialisierung (wenn diese ebenfalls nicht verfügbar ist, haben Sie ohnehin kein Glück).Wenn die Werte, mit denen Sie initialisieren, eine Liste von Werten sind, die im Objekt gespeichert werden sollen (wie die Elemente eines Vektors / Arrays oder des Real- / Imaginärteils einer komplexen Zahl), verwenden Sie die Initialisierung geschweifter Klammern, falls verfügbar.
Wenn die Werte Sie initialisieren nicht Werte gespeichert werden, sondern beschreiben den beabsichtigten Wert / Zustand des Objekts, verwenden Sie Klammern. Beispiele sind das Größenargument eines
vector
oder das Dateinamenargument einesfstream
.quelle
T {}
oder syntaktische Gründe wie die ärgerlichste Analyse ), aber im Allgemeinen denke ich, dass dies einen guten Rat gibt. Beachten Sie, dass dies meine subjektive Meinung ist, daher sollte man sich auch die anderen Antworten ansehen.type var{};
tut.Ich bin mir ziemlich sicher, dass es niemals eine universelle Richtlinie geben wird. Mein Ansatz ist es, immer geschweifte Klammern zu verwenden, die sich daran erinnern
Runde und lockige Klammern sind also nicht austauschbar. Wenn ich jedoch weiß, wo sie sich unterscheiden, kann ich in den meisten Fällen die Initialisierung von geschweiften über runden Klammern verwenden (einige der Fälle, in denen ich dies nicht kann, sind derzeit Compiler-Fehler).
quelle
int i = 0;
glaube ich nicht, dass jemand dort verwenden würdeint i{0}
, und es könnte verwirrend sein (auch,0
wenn Typint
, so würde es keine Verengung geben ). Für alles andere würde ich Juanchos Rat befolgen: lieber {}, hüte dich vor den wenigen Fällen, in denen du es nicht tun solltest. Beachten Sie, dass es nicht so viele Typen gibt, die Initialisiererlisten als Konstruktorargumente verwenden. Sie können davon ausgehen, dass Container und containerähnliche Typen (Tupel ...) diese haben, aber der meiste Code ruft den entsprechenden Konstruktor auf.int i{some floating point}
ein Fehler ist, anstatt ihn stillschweigend abzuschneiden.{}
"initialisieren" bedeuten, es sei denn, Sie können dies absolut nicht .Außerhalb von generischem Code (dh Vorlagen) können Sie (und ich) überall Klammern verwenden . Ein Vorteil ist, dass es überall funktioniert, zum Beispiel auch bei der Initialisierung in der Klasse:
oder für Funktionsargumente:
Bei Variablen, denen ich zwischen den Stilen
T t = { init };
oder nicht viel Aufmerksamkeit schenkeT t { init };
, ist der Unterschied gering und führt im schlimmsten Fall nur zu einer hilfreichen Compilermeldung über den Missbrauch einesexplicit
Konstruktors.Für Typen, die akzeptieren,
std::initializer_list
obwohl offensichtlich manchmal die Nichtkonstruktorenstd::initializer_list
benötigt werden (das klassische Beispiel iststd::vector<int> twenty_answers(20, 42);
). Es ist in Ordnung, dann keine Zahnspangen zu verwenden.Wenn es um generischen Code geht (dh in Vorlagen), sollte dieser allerletzte Absatz einige Warnungen auslösen. Folgendes berücksichtigen:
Dann
auto p = make_unique<std::vector<T>>(20, T {});
wird ein Vektor der Größe 2 erstellt, wennT
z. B.int
ein Vektor der Größe 20, wenn dies der FallT
iststd::string
. Ein sehr verräterisches Zeichen dafür, dass hier etwas sehr Falsches vor sich geht, ist, dass es keine Eigenschaft gibt, die Sie hier retten kann (z. B. bei SFINAE):std::is_constructible
In Bezug auf die Direktinitialisierung, während wir die Klammerinitialisierung verwenden, die sich von der Direktinitialisierung unterscheidet. Initialisierung genau dann, wenn kein Konstruktorstd::initializer_list
eingreift. Ebensostd::is_convertible
hilft nichts.Ich habe untersucht, ob es tatsächlich möglich ist, ein Merkmal von Hand zu rollen, das dies beheben kann, aber ich bin nicht allzu optimistisch. Auf jeden Fall denke ich nicht, dass wir viel vermissen würden, ich denke, dass die Tatsache, dass
make_unique<T>(foo, bar)
eine Konstruktion gleichbedeutendT(foo, bar)
ist, sehr intuitiv ist; vor allem angesichts dessenmake_unique<T>({ foo, bar })
ist das ziemlich unähnlich und macht nur Sinn, wennfoo
undbar
haben den gleichen Typ.Daher verwende ich für generischen Code nur geschweifte Klammern für die Wertinitialisierung (z. B.
T t {};
oderT t = {};
), was sehr praktisch ist und meiner Meinung nach der C ++ 03-Methode überlegen istT t = T();
. Andernfalls handelt es sich entweder um eine direkte Initialisierungssyntax (dhT t(a0, a1, a2);
) oder manchmal um eine Standardkonstruktion (diesT t; stream >> t;
ist der einzige Fall, in dem ich das verwende, was ich denke).Das bedeutet jedoch nicht, dass alle Klammern schlecht sind. Betrachten Sie das vorherige Beispiel mit Korrekturen:
Dies verwendet immer noch geschweifte Klammern zum Erstellen der
std::unique_ptr<T>
, obwohl der tatsächliche Typ vom Vorlagenparameter abhängtT
.quelle
make_unique<T>(20u, T {})
umT
entwederunsigned
oder zu seinstd::string
. Ich bin mir nicht sicher, was die Details angeht. (Beachten Sie, dass ich auch die Erwartungen bezüglich der direkten Initialisierung im Vergleich zur Klammerinitialisierung bezüglich z. B. Perfect-Forwarding-Funktionen kommentiert habe.) Esstd::string c("qux");
wurde nicht angegeben, dass sie als In-Class-Initialisierung funktionieren, um Mehrdeutigkeiten mit Elementfunktionsdeklarationen in der Grammatik zu vermeiden.