Konstruktoren und Member-Initialisierungslisten
Konstruktoren sind nicht-statische Memberfunktionen, die mit einer speziellen Deklaratorsyntax deklariert werden. Sie dienen zur Initialisierung von Objekten ihrer Klassentypen.
|
Ein Konstruktor darf keine Coroutine sein. |
(seit C++20) |
|
Ein Konstruktor darf keinen expliziten Objektparameter haben. |
(seit C++23) |
Inhalt |
[bearbeiten] Syntax
Konstruktoren werden mithilfe von Member-Funktionsdeklaratoren der folgenden Form deklariert:
klassen-name ( parameterliste (optional) ) ausnahme (optional) attr (optional) |
|||||||||
| Klassenname | - | ein Primärausdruck, möglicherweise gefolgt von einer Liste von Attributen und(seit C++11) möglicherweise eingeschlossen von einem Klammernpaar. | ||||||
| parameter-liste | - | Parameterliste | ||||||
| except | - |
| ||||||
| attr | - | (seit C++11) eine Liste von Attributen |
Die einzigen erlaubten Spezifizierer in den Deklarationsspezifizierern einer Konstruktordeklaration sind friend, inline, constexpr(seit C++11), consteval(seit C++20) und explicit (insbesondere ist kein Rückgabetyp erlaubt). Beachten Sie, dass auch cv- und Ref-Qualifizierer nicht erlaubt sind: die const- und volatile-Semantik eines unter Konstruktion stehenden Objekts tritt erst nach Abschluss des am weitesten abgeleiteten Konstruktors in Kraft.
Der Bezeichnerausdruck von klassen-name muss eine der folgenden Formen haben:
- In einer Friend-Deklaration ist der Bezeichnerausdruck ein qualifizierter Bezeichner, der einen Konstruktor benennt.
- Andernfalls, in einer Member-Deklaration, die zur Member-Spezifikation einer Klasse oder Klassenvorlage gehört:
- Für Klassen ist der Bezeichnerausdruck der eingeschleuste Klassenname der unmittelbar umschließenden Klasse.
- Für Klassenvorlagen ist der Bezeichnerausdruck ein Klassenname, der die aktuelle Instanziierung benennt(bis C++20)der eingeschleuste Klassenname(seit C++20) der unmittelbar umschließenden Klassenvorlage.
- Andernfalls ist der Bezeichnerausdruck ein qualifizierter Bezeichner, dessen letzter nicht-qualifizierter Bezeichner der eingeschleuste Klassenname seines Lookup-Kontextes ist.
[bearbeiten] Member-Initialisierungsliste
Der Körper einer Funktionsdefinition eines jeden Konstruktors kann, vor der öffnenden geschweiften Klammer der Verbundanweisung, die Member-Initialisierungsliste enthalten, deren Syntax das Doppelpunktzeichen : gefolgt von der durch Kommas getrennten Liste von einem oder mehreren member-initializer ist, von denen jeder die folgende Syntax hat:
klasse-oder-bezeichner ( ausdrucksliste (optional) ) |
(1) | ||||||||
| klasse-oder-bezeichner geschweifte-initialisierungsliste | (2) | (seit C++11) | |||||||
parameter-pack ... |
(3) | (seit C++11) | |||||||
| klasse-oder-bezeichner | - | Jeder Bezeichner, der ein nicht-statisches Datenmember benennt, oder jeder Typname, der entweder die Klasse selbst (für delegierende Konstruktoren) oder eine direkte oder virtuelle Basis benennt. |
| Ausdrucksliste | - | Möglicherweise leere, durch Kommas getrennte Liste der Argumente, die an den Konstruktor der Basis oder des Members übergeben werden. |
| geschweifte-initialisierungsliste | - | In geschweiften Klammern eingeschlossene Initialisierungsliste. |
| parameter-pack | - | Name eines Variadic-Template-Parameter-Packs. |
struct S { int n; S(int); // constructor declaration S() : n(7) {} // constructor definition: // ": n(7)" is the initializer list // ": n(7) {}" is the function body }; S::S(int x) : n{x} {} // constructor definition: ": n{x}" is the initializer list int main() { S s; // calls S::S() S s2(10); // calls S::S(int) }
[bearbeiten] Erklärung
Konstruktoren haben keine Namen und können nicht direkt aufgerufen werden. Sie werden aufgerufen, wenn die Initialisierung stattfindet, und sie werden nach den Regeln der Initialisierung ausgewählt. Konstruktoren ohne den explicit-Spezifizierer sind Konvertierungskonstruktoren. Konstruktoren mit einem constexpr-Spezifizierer machen ihren Typ zu einem Literal-Typ. Konstruktoren, die ohne Argumente aufgerufen werden können, sind Standardkonstruktoren. Konstruktoren, die ein anderes Objekt desselben Typs als Argument nehmen, sind Kopierkonstruktoren und Verschiebungskonstruktoren.
Bevor die Verbundanweisung, die den Funktionsrumpf des Konstruktors bildet, zu laufen beginnt, ist die Initialisierung aller direkten Basen, virtuellen Basen und nicht-statischen Datenmember abgeschlossen. Die Member-Initialisierungsliste ist der Ort, an dem die Nicht-Standard-Initialisierung dieser Unterobjekte angegeben werden kann. Für Basen, die nicht standardmäßig initialisiert werden können, und für nicht-statische Datenmember, die nicht durch Standardinitialisierung oder durch ihren Standard-Member-Initialisierer, falls vorhanden, initialisiert werden können(seit C++11), wie z.B. Member von Referenz- und const-qualifizierten Typen, müssen Member-Initialisierer angegeben werden. (Beachten Sie, dass Standard-Member-Initialisierer für nicht-statische Datenmember von Klassenvorlageninstanziierungen ungültig sein können, wenn der Member-Typ oder der Initialisierer von der Vorlage abhängt.)(seit C++11) Für anonyme Unions oder variant members, die keinen Member-Initialisierer oder Standard-Member-Initialisierer(seit C++11) haben, erfolgt keine Initialisierung.
Die Initialisierer, bei denen klasse-oder-bezeichner eine virtuelle Basisklasse benennt, werden bei der Konstruktion jeder Klasse, die nicht die am weitesten abgeleitete Klasse des zu konstruierenden Objekts ist, ignoriert.
Namen, die in ausdrucksliste oder geschweifte-initialisierungsliste erscheinen, werden im Geltungsbereich des Konstruktors ausgewertet.
class X { int a, b, i, j; public: const int& r; X(int i) : r(a) // initializes X::r to refer to X::a , b{i} // initializes X::b to the value of the parameter i , i(i) // initializes X::i to the value of the parameter i , j(this->i) // initializes X::j to the value of X::i {} };
Ausnahmen, die von Member-Initialisierern geworfen werden, können von einem Funktions-try-Block behandelt werden.
|
Wenn ein nicht-statisches Datenmember einen Standard-Member-Initialisierer hat und auch in einer Member-Initialisierungsliste vorkommt, dann wird der Member-Initialisierer verwendet und der Standard-Member-Initialisierer ignoriert. struct S { int n = 42; // default member initializer S() : n(7) {} // will set n to 7, not 42 }; |
(seit C++11) |
Referenz-Member können in einer Member-Initialisierungsliste nicht an temporäre Objekte gebunden werden.
struct A { A() : v(42) {} // Error const int& v; };
Hinweis: dasselbe gilt für Standard-Member-Initialisierer.
[bearbeiten] Operationen während Konstruktion und Zerstörung
Memberfunktionen (einschließlich virtuellen Memberfunktionen) können für ein Objekt unter Konstruktion oder Zerstörung aufgerufen werden. Ebenso kann ein Objekt unter Konstruktion oder Zerstörung Operand von typeid oder dynamic_cast sein.
Wenn diese Operationen jedoch während einer der folgenden Auswertungen durchgeführt werden, ist das Verhalten undefiniert:
|
(seit C++26) |
- eine Auswertung einer Member-Initialisierungsliste, bevor alle member-initializer für Basisklassen abgeschlossen wurden.
Delegierender KonstruktorWenn der Name der Klasse selbst als klasse-oder-bezeichner in der Member-Initialisierungsliste erscheint, muss die Liste aus diesem einen Member-Initialisierer bestehen; ein solcher Konstruktor wird als delegierender Konstruktor bezeichnet, und der durch den einzigen Member der Initialisierungsliste ausgewählte Konstruktor ist der Zielkonstruktor. In diesem Fall wird der Zielkonstruktor durch Überladungsauflösung ausgewählt und zuerst ausgeführt, dann kehrt die Kontrolle zum delegierenden Konstruktor zurück und dessen Körper wird ausgeführt. Delegierende Konstruktoren dürfen nicht rekursiv sein. class Foo { public: Foo(char x, int y) {} Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char, int) }; Erbende KonstruktorenSiehe using-Deklaration. |
(seit C++11) |
[bearbeiten] Initialisierungsreihenfolge
Die Reihenfolge der Member-Initialisierer in der Liste ist unerheblich: die tatsächliche Reihenfolge der Initialisierung ist wie folgt:
(Hinweis: Wenn die Initialisierungsreihenfolge durch das Erscheinen in den Member-Initialisierungslisten verschiedener Konstruktoren gesteuert würde, dann könnte der Destruktor nicht sicherstellen, dass die Reihenfolge der Zerstörung die Umkehrung der Reihenfolge der Konstruktion ist.)
[bearbeiten] Anmerkungen
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_delegating_constructors |
200604L |
(C++11) | Delegierende Konstruktoren |
[bearbeiten] Beispiel
#include <fstream> #include <string> #include <mutex> struct Base { int n; }; struct Class : public Base { unsigned char x; unsigned char y; std::mutex m; std::lock_guard<std::mutex> lg; std::fstream f; std::string s; Class(int x) : Base{123}, // initialize base class x(x), // x (member) is initialized with x (parameter) y{0}, // y initialized to 0 f{"test.cc", std::ios::app}, // this takes place after m and lg are initialized s(__func__), // __func__ is available because init-list is a part of constructor lg(m), // lg uses m, which is already initialized m{} // m is initialized before lg even though it appears last here {} // empty compound statement Class(double a) : y(a + 1), x(y), // x will be initialized before y, its value here is indeterminate lg(m) {} // base class initializer does not appear in the list, it is // default-initialized (not the same as if Base() were used, which is value-init) Class() try // function try block begins before the function body, which includes init list : Class(0.0) // delegate constructor { // ... } catch (...) { // exception occurred on initialization } }; int main() { Class c; Class c1(1); Class c2(0.1); }
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 194 | C++98 | Die Deklaratorsyntax von Konstruktoren erlaubte nur höchstens einen Funktionsspezifizierer (z.B. ein Konstruktor konnte nicht als inline explicit deklariert werden) |
Mehrere Funktions- spezifizierer erlaubt |
| CWG 257 | C++98 | Es war nicht spezifiziert, ob eine abstrakte Klasse Member-Initialisierer für ihre virtuellen Basisklassen |
bereitstellen sollte Spezifiziert als nicht erforderlich und solche Member-Initialisierer |
| werden bei der Ausführung ignoriert | C++98 | CWG 263 Die Deklaratorsyntax von Konstruktoren |
verbot Konstruktoren, Freunde zu sein Konstruktoren erlaubt, Freunde zu sein |
| CWG 1345 | C++98 | Anonyme Union-Member ohne Standard- Member-Initialisierer wurden standardmäßig initialisiert |
Sie werden nicht initialisiert |
| CWG 1435 | C++98 | Die Bedeutung von „Klassenname“ in der Deklaratorsyntax von Konstruktoren war unklar |
Änderte die Syntax zu einer spezialisierten Funktionsdeklaratorsyntax |
| CWG 1696 | C++98 | Referenz-Member konnten mit temporären Objekten initialisiert werden (deren Lebensdauer am Ende des Konstruktors enden würde) |
Solche Initialisierungen sind fehlerhaft |
[bearbeiten] Referenzen
- C++23 Standard (ISO/IEC 14882:2024)
- 11.4.5 Konstruktoren [class.ctor]
- 11.9.3 Initialisieren von Basen und Membern [class.base.init]
- C++20 Standard (ISO/IEC 14882:2020)
- 11.4.4 Konstruktoren [class.ctor]
- 11.10.2 Initialisieren von Basen und Membern [class.base.init]
- C++17 Standard (ISO/IEC 14882:2017)
- 15.1 Konstruktoren [class.ctor]
- 15.6.2 Initialisieren von Basen und Membern [class.base.init]
- C++14 Standard (ISO/IEC 14882:2014)
- 12.1 Konstruktoren [class.ctor]
- 12.6.2 Initialisieren von Basen und Membern [class.base.init]
- C++11 Standard (ISO/IEC 14882:2011)
- 12.1 Konstruktoren [class.ctor]
- 12.6.2 Initialisieren von Basen und Membern [class.base.init]
- C++98 Standard (ISO/IEC 14882:1998)
- 12.1 Konstruktoren [class.ctor]
- 12.6.2 Initialisieren von Basen und Membern [class.base.init]