Ich kann z.B. nicht schreiben:
class A
{
vector<int> v(12, 1);
};
Ich kann nur das schreiben:
class A
{
vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
};
Was ist die Überlegung für die Unterschiede im C++11-Sprachdesign?
Die Begründung für diese Wahl wird ausdrücklich in den verwandten erwähnt Vorschlag zum nicht statische Datenmember-Initialisierer :
Ein in Kona aufgeworfenes Problem bezüglich des Umfangs der Kennungen:
Während der Diskussion in der Kernarbeitsgruppe beim Treffen im September 2007 in Kona tauchte eine Frage über den Geltungsbereich von Bezeichnern im Initialisierer auf. Wollen wir den Klassenbereich mit der Möglichkeit der Vorwärtssuche zulassen? Oder möchten wir verlangen, dass die Initialisierer an dem Punkt, an dem sie analysiert werden, genau definiert sind?
Was ist erwünscht:
Die Motivation für die Klassenbereichssuche besteht darin, dass wir in der Lage sein möchten, alles in den Initialisierer eines nicht statischen Datenelements einzufügen, was wir in einen Mem-Initialisierer einfügen könnten, ohne die Semantik wesentlich zu ändern (Modulo-Direktinitialisierung vs. Kopierinitialisierung). :
int x();
struct S {
int i;
S() : i(x()) {} // currently well-formed, uses S::x()
// ...
static int x();
};
struct T {
int i = x(); // should use T::x(), ::x() would be a surprise
// ...
static int x();
};
Problem 1:
Leider macht dies Initialisierer der Form „( Ausdrucksliste )“ zu dem Zeitpunkt mehrdeutig, zu dem die Deklaration analysiert wird:
struct S {
int i(x); // data member with initializer
// ...
static int x;
};
struct T {
int i(x); // member function declaration
// ...
typedef int x;
};
Eine mögliche Lösung besteht darin, sich auf die bestehende Regel zu verlassen, dass eine Deklaration eine Funktion ist, wenn sie ein Objekt oder eine Funktion sein könnte:
struct S {
int i(j); // ill-formed...parsed as a member function,
// type j looked up but not found
// ...
static int j;
};
Eine ähnliche Lösung wäre die Anwendung einer anderen bestehenden Regel, die derzeit nur in Vorlagen verwendet wird, dass, wenn T ein Typ oder etwas anderes sein könnte, es etwas anderes ist; und wir können „typename“ verwenden, wenn wir wirklich einen Typ meinen:
struct S {
int i(x); // unabmiguously a data member
int j(typename y); // unabmiguously a member function
};
Beide Lösungen führen Feinheiten ein, die wahrscheinlich von vielen Benutzern missverstanden werden (wie die vielen Fragen zu comp.lang.c++ zeigen, warum „int i();“ im Blockbereich kein standardmäßig initialisiertes int deklariert). .
Die in diesem Dokument vorgeschlagene Lösung besteht darin, nur Initialisierer der Formen „= initializer-clause“ und „{ initializer-list }“ zuzulassen. Das löst das Mehrdeutigkeitsproblem in die meisten Fälle, zum Beispiel:
HashingFunction hash_algorithm{"MD5"};
Hier konnten wir die Form = nicht verwenden, da der Konstruktor von HasningFunction explizit ist. In besonders kniffligen Fällen kann es vorkommen, dass ein Typ zweimal genannt werden muss. Erwägen:
vector<int> x = 3; // error: the constructor taking an int is explicit
vector<int> x(3); // three elements default-initialized
vector<int> x{3}; // one element with the value 3
In diesem Fall müssen wir zwischen den beiden Alternativen wählen, indem wir die entsprechende Notation verwenden:
vector<int> x = vector<int>(3); // rather than vector<int> x(3);
vector<int> x{3}; // one element with the value 3
Problem 2:
Da wir keine Änderung der Regeln für die Initialisierung statischer Datenelemente vorschlagen, besteht ein weiteres Problem darin, dass das Hinzufügen des Schlüsselworts static einen wohlgeformten Initialisierer falsch formatieren könnte:
struct S {
const int i = f(); // well-formed with forward lookup
static const int j = f(); // always ill-formed for statics
// ...
constexpr static int f() { return 0; }
};
Problem 3:
Ein drittes Problem ist, dass die Klassenbereichssuche einen Kompilierzeitfehler in einen Laufzeitfehler umwandeln könnte:
struct S {
int i = j; // ill-formed without forward lookup, undefined behavior with
int j = 3;
};
(Sofern nicht vom Compiler abgefangen, wird i möglicherweise mit dem undefinierten Wert von j initialisiert.)
Der Antrag:
CWG hatte in Kona eine 6-zu-3-Strohumfrage zugunsten der Klassenbereichssuche; und genau das schlägt dieses Papier vor, wobei Initialisierer für nicht statische Datenelemente auf die Formen „= initializer-clause“ und „{ initializer-list }“ beschränkt sind.
Wir glauben:
Problem 1: Dieses Problem tritt nicht auf, da wir die ()-Notation nicht vorschlagen. Die Initialisierungsnotationen = und {} leiden nicht unter diesem Problem.
Problem 2: Das Hinzufügen des statischen Schlüsselworts macht eine Reihe von Unterschieden, dies ist der geringste davon.
Problem 3: Dies ist kein neues Problem, sondern das gleiche Problem der Initialisierungsreihenfolge, das bereits bei Konstruktor-Initialisierern besteht.
Ein möglicher Grund ist, dass das Zulassen von Klammern uns zurück zu führen würde ärgerlichste Analyse im Handumdrehen. Betrachten Sie die beiden folgenden Typen:
struct foo {};
struct bar
{
bar(foo const&) {}
};
Jetzt haben Sie einen Datenmember vom Typ bar
die Sie initialisieren möchten, also definieren Sie es als
struct A
{
bar B(foo());
};
Aber was Sie oben getan haben, ist eine Funktion namens zu deklarieren B
das gibt a zurück bar
Objekt nach Wert und nimmt ein einzelnes Argument, das eine Funktion mit der Signatur ist foo()
(gibt a foo
und nimmt keine Argumente entgegen).
Gemessen an der Anzahl und Häufigkeit der auf StackOverflow gestellten Fragen, die sich mit diesem Problem befassen, finden die meisten C++-Programmierer dies überraschend und unintuitiv. Neues hinzufügen Klammer-oder-Gleich-Initialisierer Syntax war eine Chance, diese Mehrdeutigkeit zu vermeiden und mit einer sauberen Weste zu beginnen, was wahrscheinlich der Grund dafür ist, dass sich das C++-Komitee dafür entschieden hat.
bar B{foo{}};
bar B = foo();
Beide Zeilen oben deklarieren ein Objekt namens B
des Typs bar
wie erwartet.
Abgesehen von den obigen Vermutungen möchte ich darauf hinweisen, dass Sie in Ihrem obigen Beispiel zwei völlig unterschiedliche Dinge tun.
vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
Die erste Zeile wird initialisiert v1
zu einem Vektor, der zwei Elemente enthält, 12
und 1
. Die zweite erstellt einen Vektor v2
das beinhaltet 12
Elemente, jedes initialisiert auf 1
.
Achten Sie auf diese Regel – wenn ein Typ einen Konstruktor definiert, der eine akzeptiert initializer_list<T>
dann ist dieser Konstruktor immer zuerst berücksichtigt, wenn der Initialisierer für den Typ a ist geklammerte Init-Liste. Die anderen Konstrukteure werden nur dann berücksichtigt, wenn derjenige den übernimmt initializer_list
ist nicht lebensfähig.
Ersteres ruft einen vector-Konstruktor auf, dessen Eingaben 12 und 1 sind. Letzteres ruft einen vector-Konstruktor auf, dessen Eingabe eine Initialisierungsliste ist. Sie sind grundlegend verschieden.
– druckerman
19. Juli 2014 um 3:50 Uhr
Der Grund für das Standardzitat liegt in der Grammatik Deklarator Klammer-oder-Gleich-Initialisierer (opt)
– Chris
19. Juli 2014 um 3:51 Uhr