Freigeben über


Konstruktoren (C++)

Ein Konstruktor ist eine spezielle Memberfunktion, die eine Instanz ihrer Klasse initialisiert. Verwenden Sie zum Aufrufen eines Konstruktors den Klassennamen mit Parametern in geschweiften oder runden Klammern.

class Box {
public:
    Box(int width, int length, int height){
        m_width = width;
        m_length = length;
        m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;

};
class SomeClass{
public:
    static void set_box(const Box& aBox) {}
};
int main(){

    Box box1(1, 2, 3);
    Box box2{ 2, 3, 4 };
    SomeClass::set_box(Box{ 5, 6, 7 });
}

Weitere Informationen finden Sie unter Regeln zum Deklarieren von Konstruktoren. Weitere Informationen zur Initialisierung finden Sie unter Initialisierung.

Reihenfolge der Erstellung

Ein Konstruktor führt die Arbeit in folgender Reihenfolge aus:

  1. Er ruft Basisklassen- und Memberkonstruktoren in der Reihenfolge der Deklaration auf.

  2. Wenn die Klasse von virtuellen Basisklassen abgeleitet wird, initialisiert er die virtuellen Basiszeiger des Objekts.

  3. Wenn die Klasse virtuelle Funktionen hat oder erbt, initialisiert er die virtuellen Funktionszeiger des Objekts. Virtuelle Funktionszeiger zeigen auf die virtuelle Funktionstabelle der Klasse, um eine korrekte Bindung von virtuellen Funktionsaufrufen im Code zu ermöglichen.

  4. Er führt den Code im Funktionsrumpf aus.

Das folgende Beispiel zeigt die Reihenfolge, in der Basisklassen- und Memberkonstruktoren im Konstruktor für eine abgeleitete Klasse aufgerufen werden. Zuerst wird der Basiskonstruktor aufgerufen. Anschließend werden die Basisklassenmember in der Reihenfolge initialisiert, in der sie in der Klassendeklaration stehen. Danach wird der abgeleitete Konstruktor aufgerufen.

#include <iostream>
using namespace std;

class Contained1 {
public:
    Contained1() {
        cout << "Contained1 constructor." << endl;
    }
};

class Contained2 {
public:
    Contained2() {
        cout << "Contained2 constructor." << endl;
    }
};

class Contained3 {
public:
    Contained3() {
        cout << "Contained3 constructor." << endl;
    }
};

class BaseContainer {
public:
    BaseContainer() {
        cout << "BaseContainer constructor." << endl;
    }
private:
    Contained1 c1;
    Contained2 c2;
};

class DerivedContainer : public BaseContainer {
public:
    DerivedContainer() : BaseContainer() {
        cout << "DerivedContainer constructor." << endl;
    }
private:
    Contained3 c3;
};

int main() {
    DerivedContainer dc;
    int x = 3;
}

Die Ausgabe ist wiefolgt:

Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.

Wenn ein Konstruktor eine Ausnahme auslöst, ist die Reihenfolge der Zerstörung die umgekehrte Reihenfolge der Erstellung:

  1. Der Code im Text der Konstruktorfunktion wird entladen.

  2. Basisklassen- und Memberobjekte werden in der umgekehrten Reihenfolge der Deklaration zerstört.

  3. Wenn der Konstruktor nicht delegierend ist, werden alle vollständig erstellten Basisklassenobjekte und Member zerstört. Da jedoch das Objekt selbst nicht vollständig erstellt wird, wird der Destruktor nicht ausgeführt.

Explizite Konstruktoren

Die Verwendung des explicit-Schlüsselworts für einen Konstruktor kann implizite Typkonvertierungen verhindern, wenn der Konstruktor nur einen Parameter hat oder wenn alle Parameter mit Ausnahme eines Parameters einen Standardwert aufweisen. Weitere Informationen finden Sie unter Konstruktoren (C++).

Standardkonstruktoren

Standardkonstruktoren – also Konstruktoren, die keine Parameter haben – folgen etwas anderen Regeln.

Wenn keine Konstruktoren in einer Klasse deklariert sind, stellt der Compiler einen Standardkonstruktor bereit:

class Box {
    int m_width;
    int m_length;
    int m_height;
};

int main(){

    Box box1{};
    Box box2;
}

Wenn Sie einen Standardkonstruktor aufrufen und versuchen, runde Klammern zu verwenden, wird eine Warnung ausgegeben:

class myclass{};
int main(){
myclass mc();     // warning C4930: prototyped function not called (was a variable definition intended?)
}

Dies ist ein Beispiel für das "Most Vexing Parse"-Problem. Da der Beispielsausdruck entweder als Deklaration einer Funktion oder als Aufruf eines Standardkonstruktors interpretiert werden kann, und da C++-Parser Deklarationen bevorzugen, wird der Ausdruck als Funktionsdeklaration behandelt. Weitere Informationen finden Sie im Artikel zu Most Vexing Parse.

Wenn nicht standardmäßige Konstruktoren deklariert werden, stellt der Compiler keinen Standardkonstruktor bereit:

class Box {
public:
    Box(int width, int length, int height){
       m_width = width;
       m_length = length;
       m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;

};

int main(){

    Box box1(1, 2, 3);
    Box box2{ 2, 3, 4 };
    Box box4;     // compiler error C2512: no appropriate default constructor available
}

Wenn eine Klasse keinen Standardkonstruktor hat, kann ein Array von Objekten dieser Klasse nicht allein mithilfe von Syntax in eckigen Klammern erstellt werden. Beispielsweise kann im vorherigen Codeblock ein Array von Feldern nicht folgendermaßen deklariert werden:

Box boxes[3];    // compiler error C2512: no appropriate default constructor available

Sie können jedoch einen Satz von Initialisiererlisten verwenden, um ein Array von Feldern zu initialisieren:

Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };

Kopier- und Bewegungskonstruktoren

Ein Kopierkonstruktor verwendet einen Verweis auf ein Objekt, um es zu kopieren. Wenn Sie keinen Kopierkonstruktor für eine Klasse angeben, erstellt der Compiler einen Standardkopierkonstruktor, selbst wenn ein Kopiervorgang, ein Verschiebevorgang oder Destruktor deklariert wurde. Weitere Informationen finden Sie unter Regeln zum Deklarieren von Konstruktoren.

Ein Bewegungskonstruktor aktiviert die Übertragung des reservierten Arbeitsspeichers von einem Objekt zu einem anderen. Weitere Informationen finden Sie unter Gewusst wie: Schreiben eines Bewegungskonstruktors.

Explizit auf den Standardwert festgelegte und gelöschte Konstruktoren

Sie können Kopierkonstruktoren, Standardkonstruktoren, Kopierzuweisungsoperatoren und Destruktoren explizit auf den Standardwert festlegen, aber die explizite Festlegung von Bewegungskonstruktoren und Bewegungszuweisungsoperatoren auf den Standardwert wird nicht unterstützt. (Diese Unterstützung ist in Visual Studio 2015 vorhanden.) Sie können spezielle Funktionen explizit löschen. Weitere Informationen finden Sie unter Explizit vorgegebene und gelöschte Funktionen.

Konstruktoren in abgeleiteten Klassen

Ein abgeleiteter Klassenkonstruktor ruft immer einen Basisklassenkonstruktor auf, damit er immer auf vollständig erstellte Basisklassen zurückgreifen kann, bevor zusätzliche Arbeit erforderlich ist. Die Basisklassenkonstruktoren werden in der Reihenfolge der Ableitung aufgerufen. Beispiel: Wenn ClassA von ClassB abgeleitet ist, die von ClassC abgeleitet ist, wird zuerst der ClassC-Konstruktor, dann der ClassB-Konstruktor und anschließend der ClassA-Konstruktor aufgerufen.

Wenn eine Basisklasse keinen Standardkonstruktor hat, müssen Sie die Parameter für den Basisklassenkonstruktor im abgeleiteten Klassenkonstruktor angeben:

class Box {
public:
    Box(int width, int length, int height){
       m_width = width;
       m_length = length;
       m_height = height;
    }

private:
    int m_width;
    int m_length;
    int m_height;
};

class StorageBox : public Box {
public:
    StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){
        m_label = label;
    }
private:
    string m_label;
};

int main(){

    const string aLabel = "aLabel";
    StorageBox sb(1, 2, 3, aLabel);
} 

Konstruktoren für Klassen mit Mehrfachvererbung

Wenn eine Klasse von mehreren Basisklassen abgeleitet ist, werden die Basisklassenkonstruktoren in der Reihenfolge aufgerufen, in der sie in der Deklaration der abgeleiteten Klasse aufgelistet sind:

#include <iostream>
using namespace std;

class BaseClass1 {
public:
    BaseClass1() {
        cout << "BaseClass1 constructor." << endl;
    }
};
class BaseClass2 {
public:
    BaseClass2() {
        cout << "BaseClass2 constructor." << endl;
    }
};
class BaseClass3{
public:
    BaseClass3() {
        cout << "BaseClass3 constructor." << endl;
    }
};
class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3  {
public:
    DerivedClass() {
        cout << "DerivedClass constructor." << endl;
    }
};

int main() {
    DerivedClass dc;
}

Die folgende Ausgabe sollte angezeigt werden:

BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.

Virtuelle Funktionen in Konstruktoren

Beim Aufrufen von virtuellen Funktionen in Konstruktoren wird zur Vorsicht geraten. Da der Basisklassenkonstruktor immer vor dem abgeleiteten Klassenkonstruktor aufgerufen wird, ist die im Basiskonstruktor aufgerufene Funktion die Basisklassenversion und nicht die Version der abgeleiteten Klasse. Im folgenden Beispiel bewirkt die Erstellung von DerivedClass, dass die BaseClass-Implementierung von print_it() ausgeführt wird, bevor der DerivedClass-Konstruktor veranlasst, dass die DerivedClass-Implementierung von print_it() ausgeführt wird:

#include <iostream>
using namespace std;

class BaseClass{
public:
    BaseClass(){
        print_it();
    }
    virtual void print_it() {
        cout << "BaseClass print_it" << endl;
    }
};

class DerivedClass : public BaseClass {
public:
    DerivedClass() {
        print_it();
    }
    virtual void print_it(){
        cout << "Derived Class print_it" << endl;
    }
};

int main() {

    DerivedClass dc;
}

Die Ausgabe ist wiefolgt:

BaseClass print_it
Derived Class print_it

Konstruktoren und zusammengesetzte Klassen

Klassen, die Klassentypmember enthalten, werden als zusammengesetzte Klassen bezeichnet. Wenn ein Klassentypmember einer zusammengesetzten Klasse erstellt wird, wird der Konstruktor vor dem Konstruktor der Klasse aufgerufen. Wenn einer enthaltenen Klasse ein Standardkonstruktor fehlt, müssen Sie eine Initialisierungsliste im Konstruktor der zusammengesetzten Klasse verwenden. Wenn Sie im StorageBox-Beispiel oben den Typ der m_label-Membervariable in eine neue Label-Klasse ändern, müssen Sie den Basisklassenkonstruktor aufrufen und die m_label-Variable im StorageBox-Konstruktor initialisieren:

class Label {
public:
    Label(const string& name, const string& address) { m_name = name; m_address = address; }
    string m_name;
    string m_address;
};

class StorageBox : public Box {
public:
    StorageBox(int width, int length, int height, Label label) 
        : Box(width, length, height), m_label(label){}
private:
    Label m_label;
};

int main(){
// passing a named Label
    Label label1{ "some_name", "some_address" };
    StorageBox sb1(1, 2, 3, label1);

    // passing a temporary label
    StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });

    // passing a temporary label as an initializer list
    StorageBox sb3(1, 2, 3, {"myname", "myaddress"});
}

Delegierende Konstruktoren

Ein delegierender Konstruktor ruft einen anderen Konstruktor in derselben Klasse auf, um einen Teil der Initialisierung auszuführen. Im folgenden Beispiel hat die abgeleitete Klasse drei Konstruktoren. Der zweite Konstruktor delegiert an den ersten, und der dritte Konstruktor delegiert an den zweiten:

#include <iostream>
using namespace std;

class ConstructorDestructor {
public:
    ConstructorDestructor() {
        cout << "ConstructorDestructor default constructor." << endl;
    }
    ConstructorDestructor(int int1) {
        cout << "ConstructorDestructor constructor with 1 int." << endl;
    }
    ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
        cout << "ConstructorDestructor constructor with 2 ints." << endl;
        
        throw exception();
    }
    ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
        cout << "ConstructorDestructor constructor with 3 ints." << endl;
    }
    ~ConstructorDestructor() {
        cout << "ConstructorDestructor destructor." << endl;
    }
};

int main() {
    ConstructorDestructor dc(1, 2, 3);
}

Die Ausgabe ist wiefolgt:

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.

Das von den Konstruktoren erstellte Objekt wird vollständig initialisiert, sobald jeder Konstruktor abgeschlossen ist. DerivedContainer(int int1) ist erfolgreich, aber DerivedContainer(int int1, int int2) schlägt fehl, und der Destruktor wird aufgerufen.

class ConstructorDestructor {
public:
    ConstructorDestructor() {
        cout << "ConstructorDestructor default constructor." << endl;
    }
    ConstructorDestructor(int int1) {
        cout << "ConstructorDestructor constructor with 1 int." << endl;
    }
    ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
        cout << "ConstructorDestructor constructor with 2 ints." << endl;
        throw exception();
    }
    ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
        cout << "ConstructorDestructor constructor with 3 ints." << endl;
    }

    ~ConstructorDestructor() {
        cout << "ConstructorDestructor destructor." << endl;
    }
};

int main() {

    try {
        ConstructorDestructor cd{ 1, 2, 3 };
    }
    catch (const exception& ex){
    }
}

Ausgabe:

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.

Weitere Informationen finden Sie unter Einheitliche Initialisierung und Delegierung von Konstruktoren.

Siehe auch

Referenz

Spezielle Memberfunktionen (C++)