Der Codierungsstil ist letztendlich subjektiv und es ist sehr unwahrscheinlich, dass sich daraus wesentliche Leistungsvorteile ergeben. Aber hier ist, was ich sagen würde, dass Sie von der liberalen Verwendung der einheitlichen Initialisierung profitieren:
Minimiert redundante Typenamen
Folgendes berücksichtigen:
vec3 GetValue()
{
return vec3(x, y, z);
}
Warum muss ich vec3
zweimal tippen ? Gibt es einen Grund dafür? Der Compiler weiß gut und genau, was die Funktion zurückgibt. Warum kann ich nicht einfach sagen: "Rufen Sie den Konstruktor dessen auf, was ich mit diesen Werten zurückgebe, und geben Sie ihn zurück?" Mit der einheitlichen Initialisierung kann ich:
vec3 GetValue()
{
return {x, y, z};
}
Funktioniert alles.
Noch besser ist es für Funktionsargumente. Bedenken Sie:
void DoSomething(const std::string &str);
DoSomething("A string.");
Das geht, ohne einen Typnamen eingeben zu müssen, denn std::string
man kann sich aus einem const char*
implizit aufbauen . Das ist großartig. Aber was ist, wenn diese Zeichenfolge stammt, sagen Sie RapidXML. Oder eine Lua-Saite. Nehmen wir an, ich kenne die Länge der Saite von vorne. Der std::string
Konstruktor, der a annimmt const char*
, muss die Länge der Zeichenfolge annehmen, wenn ich nur a übergebe const char*
.
Es gibt jedoch eine Überladung, die explizit eine Länge hat. Aber , es zu benutzen, würde ich dies tun: DoSomething(std::string(strValue, strLen))
. Warum ist dort der zusätzliche Typenname angegeben? Der Compiler kennt den Typ. Genau wie bei auto
können wir zusätzliche Typnamen vermeiden:
DoSomething({strValue, strLen});
Es funktioniert einfach Keine Typnamen, keine Aufregung, nichts. Der Compiler macht seine Arbeit, der Code ist kürzer und alle sind glücklich.
Zugegeben, es gibt Argumente dafür, dass die erste Version ( DoSomething(std::string(strValue, strLen))
) besser lesbar ist. Das heißt, es ist offensichtlich, was los ist und wer was tut. Das ist bis zu einem gewissen Grad wahr; Um den einheitlichen, auf der Initialisierung basierenden Code zu verstehen, muss der Funktionsprototyp betrachtet werden. Dies ist der gleiche Grund, warum manche sagen, Sie sollten Parameter niemals als Nicht-Konstanten-Referenz übergeben: Damit Sie auf der Aufruf-Site sehen können, ob ein Wert geändert wird.
Aber das Gleiche könnte man sagen auto
; Um zu wissen, was Sie daraus machen, auto v = GetSomething();
müssen Sie die Definition von GetSomething
. Aber das hat nicht aufgehört auto
, mit fast rücksichtsloser Hingabe verwendet zu werden, sobald Sie Zugriff darauf haben. Persönlich denke ich, dass es in Ordnung sein wird, wenn Sie sich daran gewöhnt haben. Vor allem mit einer guten IDE.
Erhalten Sie niemals das nervigste Parse
Hier ist ein Code.
class Bar;
void Func()
{
int foo(Bar());
}
Pop Quiz: Was ist das foo
? Wenn Sie mit "eine Variable" geantwortet haben, liegen Sie falsch. Es ist eigentlich der Prototyp einer Funktion, die eine Funktion als Parameter verwendet, die a zurückgibt Bar
, und der foo
Rückgabewert der Funktion ist ein int.
Dies nennt man C ++ "Most Vexing Parse", weil es für einen Menschen absolut keinen Sinn ergibt. Aber die Regeln von C ++ verlangen dies leider: Wenn es möglicherweise als Funktionsprototyp interpretiert werden kann, dann ist es das auch. Das Problem ist Bar()
: Das könnte eines von zwei Dingen sein. Es kann sich um einen Typ handeln Bar
, der einen temporären Typ erstellt. Oder es könnte eine Funktion sein, die keine Parameter akzeptiert und a zurückgibt Bar
.
Eine einheitliche Initialisierung kann nicht als Funktionsprototyp interpretiert werden:
class Bar;
void Func()
{
int foo{Bar{}};
}
Bar{}
schafft immer eine temporäre. int foo{...}
Erstellt immer eine Variable.
Es gibt viele Fälle, die Sie verwenden möchten, Typename()
aber aufgrund der Parsing-Regeln von C ++ einfach nicht können. Mit Typename{}
gibt es keine Mehrdeutigkeit.
Gründe, nicht zu
Die einzige wirkliche Kraft, die Sie aufgeben, ist die Verengung. Sie können einen kleineren Wert nicht mit einem größeren Wert mit einheitlicher Initialisierung initialisieren.
int val{5.2};
Das wird nicht kompiliert. Sie können dies mit einer altmodischen Initialisierung tun, jedoch nicht mit einer einheitlichen Initialisierung.
Dies wurde teilweise durchgeführt, damit Initialisierungslisten tatsächlich funktionieren. Andernfalls würde es in Bezug auf die Typen von Initialisierungslisten viele mehrdeutige Fälle geben.
Natürlich könnten einige argumentieren, dass ein solcher Code es verdient , nicht kompiliert zu werden. Ich persönlich stimme dem zu. Einengung ist sehr gefährlich und kann zu unangenehmem Verhalten führen. Es ist wahrscheinlich am besten, diese Probleme frühzeitig in der Compiler-Phase zu erkennen. Zumindest deutet die Einschränkung darauf hin, dass jemand nicht zu sehr über den Code nachdenkt.
Beachten Sie, dass Compiler Sie in der Regel vor solchen Situationen warnen, wenn Ihre Warnstufe hoch ist. Das alles macht die Warnung zu einem erzwungenen Fehler. Einige könnten sagen, dass Sie das trotzdem tun sollten;)
Es gibt einen weiteren Grund, nicht:
std::vector<int> v{100};
Was macht das? Es könnte ein vector<int>
mit einhundert vorkonstruiertes Objekt entstehen. Oder es könnte ein vector<int>
mit 1 Element erstellt werden, dessen Wert ist 100
. Beides ist theoretisch möglich.
In Wirklichkeit tut es das letztere.
Warum? Initialisierungslisten verwenden dieselbe Syntax wie die einheitliche Initialisierung. Es muss also einige Regeln geben, die erklären, was im Falle von Mehrdeutigkeiten zu tun ist. Die Regel ist ziemlich einfach: Wenn der Compiler kann eine Initialisiererliste Konstruktor mit einem Doppelpack initialisierte Liste verwendet, dann es wird . Da vector<int>
es einen Initialisiererlistenkonstruktor gibt, der benötigt initializer_list<int>
und {100} gültig sein könnte initializer_list<int>
, muss dies der Fall sein .
Um den Sizing-Konstruktor zu erhalten, müssen Sie ()
anstelle von verwenden {}
.
Beachten Sie, dass vector
dies nicht passieren würde , wenn dies von etwas wäre, das nicht in eine Ganzzahl konvertierbar wäre. Eine initializer_list würde nicht in den Initializer-List-Konstruktor dieses vector
Typs passen , und daher kann der Compiler frei aus den anderen Konstruktoren auswählen.