Vor C ++ 11 konnten wir nur eine In-Class-Initialisierung für statische const-Elemente vom Integral- oder Aufzählungstyp durchführen. Stroustrup erläutert dies in seinen C ++ - FAQ anhand des folgenden Beispiels:
class Y {
const int c3 = 7; // error: not static
static int c4 = 7; // error: not const
static const float c5 = 7; // error: not integral
};
Und die folgenden Überlegungen:
Warum gibt es diese unbequemen Einschränkungen? Eine Klasse wird normalerweise in einer Header-Datei deklariert, und eine Header-Datei ist normalerweise in vielen Übersetzungseinheiten enthalten. Um jedoch komplizierte Linkerregeln zu vermeiden, erfordert C ++, dass jedes Objekt eine eindeutige Definition hat. Diese Regel würde verletzt, wenn C ++ die Definition von Entitäten in der Klasse zulässt, die als Objekte im Speicher gespeichert werden müssen.
In C ++ 11 werden diese Einschränkungen jedoch gelockert, sodass nicht statische Elemente in der Klasse initialisiert werden können (§12.6.2 / 8):
Wenn in einem nicht delegierenden Konstruktor ein bestimmtes nicht statisches Datenelement oder eine Basisklasse nicht durch eine mem-initializer-id gekennzeichnet ist (einschließlich des Falls, in dem keine mem-initializer-Liste vorhanden ist, weil der Konstruktor keinen ctor-initializer hat ) und die Entität ist also keine virtuelle Basisklasse einer abstrakten Klasse (10.4)
- Wenn die Entität ein nicht statisches Datenelement ist, das einen Klammer-oder-Gleich-Initialisierer hat , wird die Entität wie in 8.5 angegeben initialisiert.
- Andernfalls wird keine Initialisierung durchgeführt, wenn die Entität ein Variantenmitglied ist (9.5).
- Andernfalls wird die Entität standardmäßig initialisiert (8.5).
Abschnitt 9.4.2 ermöglicht auch die In-Class-Initialisierung von nicht konstanten statischen Elementen, wenn diese mit dem Bezeichner markiert sind constexpr
.
Was ist nun mit den Gründen für die Einschränkungen in C ++ 03 passiert? Akzeptieren wir einfach die "komplizierten Linker-Regeln" oder hat sich etwas anderes geändert, das die Implementierung erleichtert?
quelle
Antworten:
Die kurze Antwort ist, dass sie den Linker ungefähr gleich gehalten haben, auf Kosten des Compilers, der noch komplizierter als zuvor ist.
Das heißt, anstatt dass dies zu mehreren Definitionen führt, die der Linker aussortieren muss, führt dies immer noch nur zu einer Definition, und der Compiler muss sie aussortieren.
Es führt auch zu etwas komplexeren Regeln, die der Programmierer ebenfalls aussortieren muss, aber es ist meistens einfach genug, dass es keine große Sache ist. Die zusätzlichen Regeln werden eingeführt, wenn für ein einzelnes Mitglied zwei verschiedene Initialisierer angegeben sind:
Die zusätzlichen Regeln an dieser Stelle befassen sich nun mit dem Wert, der zum Initialisieren verwendet wird,
a
wenn Sie den nicht standardmäßigen Konstruktor verwenden. Die Antwort darauf ist ziemlich einfach: Wenn Sie einen Konstruktor verwenden, der keinen anderen Wert angibt1234
, wird der zum Initialisieren verwendet.a
Wenn Sie jedoch einen Konstruktor verwenden, der einen anderen Wert angibt, wird der Wert1234
grundsätzlich ignoriert.Beispielsweise:
Ergebnis:
quelle
Ich denke, dass die Argumentation möglicherweise geschrieben wurde, bevor die Vorlagen fertiggestellt wurden. Schließlich waren / waren die "komplizierten Linker-Regeln", die für In-Class-Initialisierer statischer Elemente in der Klasse erforderlich waren, bereits erforderlich, damit C ++ 11 statische Elemente von Vorlagen unterstützt.
Erwägen
Das Problem für den Compiler ist in allen drei Fällen dasselbe: In welcher Übersetzungseinheit sollte er die Definition
s
und den Code ausgeben, die zur Initialisierung erforderlich sind? Die einfache Lösung besteht darin, es überall auszugeben und vom Linker aussortieren zu lassen. Deshalb haben die Linker bereits Dinge wie unterstützt__declspec(selectany)
. Ohne C ++ 03 wäre es einfach nicht möglich gewesen. Deshalb musste der Linker nicht erweitert werden.Um es klarer auszudrücken: Ich denke, die Argumentation im alten Standard ist einfach falsch.
AKTUALISIEREN
Wie Kapil betonte, ist mein erstes Beispiel im aktuellen Standard (C ++ 14) nicht einmal erlaubt. Ich habe es trotzdem belassen, weil es IMO der schwierigste Fall für die Implementierung ist (Compiler, Linker). Mein Punkt ist: Auch dieser Fall ist nicht schwieriger als das, was bereits erlaubt ist, z. B. bei der Verwendung von Vorlagen.
quelle
Theoretisch ist der
So why do these inconvenient restrictions exist?...
Grund gültig, kann aber leicht umgangen werden, und genau das tut C ++ 11.Wenn Sie umfassen eine Datei enthält sie einfach die Datei und missachtet jede Initialisierung. Die Mitglieder werden nur initialisiert, wenn Sie die Klasse instanziieren .
Mit anderen Worten, die Initialisierung ist immer noch an den Konstruktor gebunden, nur die Notation ist anders und bequemer. Wenn der Konstruktor nicht aufgerufen wird, werden die Werte nicht initialisiert.
Wenn der Konstruktor aufgerufen wird, werden die Werte mit einer In-Class-Initialisierung initialisiert, falls vorhanden, oder der Konstruktor kann diese mit einer eigenen Initialisierung überschreiben. Der Initialisierungspfad ist im Wesentlichen der gleiche, dh über den Konstruktor.
Dies geht aus Stroustrups eigenen FAQ zu C ++ 11 hervor.
quelle
Y::c3
in der Frage umgehen ? Soweit ich weiß,c3
wird es immer initialisiert, es sei denn, es gibt einen Konstruktor, der den in der Deklaration angegebenen Standard überschreibt.