Konstruktoren (C++)

Um anzupassen, wie eine Klasse ihre Member initialisiert oder Funktionen aufruft, wenn ein Objekt der Klasse erstellt wird, definieren Sie einen Konstruktor. Ein Konstruktor hat den gleichen Namen wie die Klasse und weist keinen Rückgabewert auf. Sie können beliebig viele überladene Konstruktoren definieren, um die Initialisierung auf verschiedene Weise anzupassen. In der Regel verfügen Konstruktoren über öffentliche Barrierefreiheit, sodass Code außerhalb der Klassendefinition oder Vererbungshierarchie Objekte der Klasse erstellen kann. Sie können aber auch einen Konstruktor als protected oder private.

Konstruktoren können optional eine Memberinitialisierungsliste verwenden. Es ist eine effizientere Möglichkeit, Klassenmember zu initialisieren, als Werte im Konstruktortext zuzuweisen. Das folgende Beispiel zeigt eine Klasse Box mit drei überladenen Konstruktoren. Init-Listen der letzten beiden Member verwenden:

class Box {
public:
    // Default constructor
    Box() {}

    // Initialize a Box with equal dimensions (i.e. a cube)
    explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list
    {}

    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

    int Volume() { return m_width * m_length * m_height; }

private:
    // Will have value of 0 when default constructor is called.
    // If we didn't zero-init here, default constructor would
    // leave them uninitialized with garbage values.
    int m_width{ 0 };
    int m_length{ 0 };
    int m_height{ 0 };
};

Wenn Sie eine Instanz einer Klasse deklarieren, wählt der Compiler aus, welcher Konstruktor basierend auf den Regeln der Überladungsauflösung aufgerufen werden soll:

int main()
{
    Box b; // Calls Box()

    // Using uniform initialization (preferred):
    Box b2 {5}; // Calls Box(int)
    Box b3 {5, 8, 12}; // Calls Box(int, int, int)

    // Using function-style notation:
    Box b4(2, 4, 6); // Calls Box(int, int, int)
}
  • Konstruktoren können als inline, explicit, , friendoder constexpr.
  • Ein Konstruktor kann ein Objekt initialisieren, das als constoder volatileconst volatile. Das Objekt wird const nach Abschluss des Konstruktors.
  • Um einen Konstruktor in einer Implementierungsdatei zu definieren, geben Sie ihm einen qualifizierten Namen wie jede andere Memberfunktion: Box::Box(){...}.

Memberinitialisierungslisten

Ein Konstruktor kann optional über eine Memberinitialisierungsliste verfügen, die Klassenmember initialisiert, bevor der Konstruktortext ausgeführt wird. (Eine Memberinitialisierungsliste ist nicht identisch mit einer Initialisierungsliste vom Typ std::initializer_list<T>.)

Bevorzugen Sie Memberinitialisiererlisten über das Zuweisen von Werten im Textkörper des Konstruktors. Eine Memberinitialisierungsliste initialisiert die Mitglieder direkt. Das folgende Beispiel zeigt die Elementinitialisierungsliste, die aus allen identifier(argument) Ausdrücken nach dem Doppelpunkt besteht:

    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

Der Bezeichner muss auf ein Klassenmitglied verweisen; sie wird mit dem Wert des Arguments initialisiert. Das Argument kann einer der Konstruktorparameter, ein Funktionsaufruf oder ein std::initializer_list<T>.

const Member und Member des Referenztyps müssen in der Memberinitialisierungsliste initialisiert werden.

Um sicherzustellen, dass Basisklassen vollständig initialisiert werden, bevor der abgeleitete Konstruktor ausgeführt wird, rufen Sie alle parametrisierten Basisklassenkonstruktoren in der Initialisierungsliste auf.

Standardkonstruktoren

Standardkonstruktoren haben in der Regel keine Parameter, aber sie können Parameter mit Standardwerten haben.

class Box {
public:
    Box() { /*perform any required default initialization steps*/}

    // All params have default values
    Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l){}
...
}

Standardkonstruktoren sind eine der speziellen Memberfunktionen. Wenn keine Konstruktoren in einer Klasse deklariert werden, stellt der Compiler einen impliziten inline Standardkonstruktor bereit.

#include <iostream>
using namespace std;

class Box {
public:
    int Volume() {return m_width * m_height * m_length;}
private:
    int m_width { 0 };
    int m_height { 0 };
    int m_length { 0 };
};

int main() {
    Box box1; // Invoke compiler-generated constructor
    cout << "box1.Volume: " << box1.Volume() << endl; // Outputs 0
}

Wenn Sie auf einen impliziten Standardkonstruktor angewiesen sind, müssen Sie Elemente in der Klassendefinition initialisieren, wie im vorherigen Beispiel gezeigt. Ohne diese Initialisierer würden die Member nicht initialisiert, und der Volume()-Aufruf würde einen Garbage Value erzeugen. Im Allgemeinen empfiehlt es sich, Member auf diese Weise zu initialisieren, auch wenn sie nicht auf einen impliziten Standardkonstruktor vertrauen.

Sie können verhindern, dass der Compiler einen impliziten Standardkonstruktor generiert, indem Sie ihn als gelöscht definieren:

    // Default constructor
    Box() = delete;

Ein vom Compiler generierter Standardkonstruktor wird als gelöscht definiert, wenn klassenmember nicht standardmäßig konstruierbar sind. Beispielsweise müssen alle Member des Klassentyps und deren Klassentypmember über einen Standardkonstruktor und Destruktoren verfügen, auf die zugegriffen werden kann. Alle Datenmember des Referenztyps und alle const Member müssen über einen Standardelementinitialisierer verfügen.

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

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

Diese Anweisung ist ein Beispiel für das Problem "Most Vexing Parse". Sie können entweder als Funktionsdeklaration oder als Aufruf eines Standardkonstruktors interpretiert werden myclass md(); . Da C++-Parser Deklarationen gegenüber anderen Dingen bevorzugen, wird der Ausdruck als Funktionsdeklaration behandelt. Weitere Informationen finden Sie unter "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 box3; // C2512: no appropriate default constructor available
}

Wenn eine Klasse keinen Standardkonstruktor aufweist, kann ein Array von Objekten dieser Klasse nicht allein mithilfe der Syntax in eckigen Klammern erstellt werden. Ein Array von Boxen kann beispielsweise aufgrund des vorherigen Codeblocks nicht wie folgt deklariert werden:

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

Sie können jedoch eine Reihe von Initialisierungslisten verwenden, um ein Array von Box-Objekten zu initialisieren:

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

Weitere Informationen finden Sie unter Initialisierer.

Kopierkonstruktoren

Ein Kopierkonstruktor initialisiert ein Objekt, indem die Memberwerte aus einem Objekt desselben Typs kopiert werden. Wenn Ihre Klassenmber alle einfachen Typen wie skalare Werte sind, reicht der vom Compiler generierte Kopierkonstruktor aus, und Sie müssen keine eigenen definieren. Wenn ihre Klasse eine komplexere Initialisierung erfordert, müssen Sie einen benutzerdefinierten Kopierkonstruktor implementieren. Wenn z. B. ein Klassenelement ein Zeiger ist, müssen Sie einen Kopierkonstruktor definieren, um neuen Arbeitsspeicher zuzuweisen und die Werte aus dem Zeigerobjekt des anderen zu kopieren. Der vom Compiler generierte Kopierkonstruktor kopiert einfach den Zeiger, sodass der neue Zeiger weiterhin auf den Speicherspeicherort des anderen zeigt.

Ein Kopierkonstruktor verfügt möglicherweise über eine der folgenden Signaturen:

    Box(Box& other); // Avoid if possible--allows modification of other.
    Box(const Box& other);
    Box(volatile Box& other);
    Box(volatile const Box& other);

    // Additional parameters OK if they have default values
    Box(Box& other, int i = 42, string label = "Box");

Wenn Sie einen Kopierkonstruktor definieren, sollten Sie auch einen Kopierzuweisungsoperator (=) definieren. Weitere Informationen finden Sie unter Zuordnungs - und Kopierkonstruktoren und Kopieren von Zuordnungsoperatoren.

Sie können verhindern, dass Das Objekt kopiert wird, indem Sie den Kopierkonstruktor als gelöscht definieren:

    Box (const Box& other) = delete;

Beim Versuch, das Objekt zu kopieren, tritt der Fehler C2280 auf: Der Versuch, auf eine gelöschte Funktion zu verweisen.

Verschieben von Konstruktoren

Ein Verschiebungskonstruktor ist eine spezielle Memberfunktion, die den Besitz der Daten eines vorhandenen Objekts in eine neue Variable verschiebt, ohne die ursprünglichen Daten zu kopieren. Es verwendet einen Rvalue-Verweis als ersten Parameter, und alle späteren Parameter müssen Standardwerte aufweisen. Verschieben von Konstruktoren kann die Effizienz Ihres Programms beim Übergeben großer Objekte erheblich steigern.

Box(Box&& other);

Der Compiler wählt einen Verschiebungskonstruktor aus, wenn das Objekt von einem anderen Objekt desselben Typs initialisiert wird, wenn das andere Objekt zerstört werden soll und seine Ressourcen nicht mehr benötigt. Das folgende Beispiel zeigt einen Fall, wenn ein Verschiebungskonstruktor durch Überladungsauflösung ausgewählt wird. Im Konstruktor, der aufruft get_Box(), ist der zurückgegebene Wert ein X-Wert (eXpiring-Wert). Sie wird keiner Variablen zugewiesen und ist daher nicht im Bereich. Um Motivation für dieses Beispiel bereitzustellen, geben wir Box einen großen Vektor von Zeichenfolgen, die ihren Inhalt darstellen. Anstatt den Vektor und seine Zeichenfolgen zu kopieren, stiehlt der Konstruktor sie vom ablaufenden Wert "box", damit der Vektor jetzt zum neuen Objekt gehört. Der Aufruf ist std::move alles, was erforderlich ist, da beide vector Klassen string ihre eigenen Verschiebungskonstruktoren implementieren.

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

class Box {
public:
    Box() { std::cout << "default" << std::endl; }
    Box(int width, int height, int length)
       : m_width(width), m_height(height), m_length(length)
    {
        std::cout << "int,int,int" << std::endl;
    }
    Box(Box& other)
       : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
    {
        std::cout << "copy" << std::endl;
    }
    Box(Box&& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
    {
        m_contents = std::move(other.m_contents);
        std::cout << "move" << std::endl;
    }
    int Volume() { return m_width * m_height * m_length; }
    void Add_Item(string item) { m_contents.push_back(item); }
    void Print_Contents()
    {
        for (const auto& item : m_contents)
        {
            cout << item << " ";
        }
    }
private:
    int m_width{ 0 };
    int m_height{ 0 };
    int m_length{ 0 };
    vector<string> m_contents;
};

Box get_Box()
{
    Box b(5, 10, 18); // "int,int,int"
    b.Add_Item("Toupee");
    b.Add_Item("Megaphone");
    b.Add_Item("Suit");

    return b;
}

int main()
{
    Box b; // "default"
    Box b1(b); // "copy"
    Box b2(get_Box()); // "move"
    cout << "b2 contents: ";
    b2.Print_Contents(); // Prove that we have all the values

    char ch;
    cin >> ch; // keep window open
    return 0;
}

Wenn eine Klasse keinen Verschiebungskonstruktor definiert, generiert der Compiler einen impliziten Konstruktor, wenn kein vom Benutzer deklarierter Kopierkonstruktor, Kopierzuordnungsoperator, Verschiebungszuweisungsoperator oder Destruktor vorhanden ist. Wenn kein expliziter oder impliziter Verschiebungskonstruktor definiert ist, verwenden Vorgänge, die andernfalls einen Verschiebungskonstruktor verwenden würden, stattdessen den Kopierkonstruktor. Wenn eine Klasse einen Verschiebungskonstruktor oder einen Zuweisungsoperator deklariert, wird der implizit deklarierte Kopierkonstruktor als gelöscht definiert.

Ein implizit deklarierter Verschiebungskonstruktor wird als gelöscht definiert, wenn elemente, die Klassentypen sind, keinen Destruktor besitzen oder der Compiler nicht bestimmen kann, welcher Konstruktor für den Verschiebungsvorgang verwendet werden soll.

Weitere Informationen zum Schreiben eines nicht trivialen Verschiebungskonstruktors finden Sie unter Move Constructors and Move Assignment Operators (C++).

Explizit standardmäßige und gelöschte Konstruktoren

Sie können explizit Standardkopiekonstruktoren, Standardkonstruktoren , Konstruktoren verschieben, Zuordnungsoperatoren kopieren, Zuordnungsoperatoren verschieben und Destruktoren destruktoren. Sie können alle speziellen Memberfunktionen explizit löschen .

class Box2
{
public:
    Box2() = delete;
    Box2(const Box2& other) = default;
    Box2& operator=(const Box2& other) = default;
    Box2(Box2&& other) = default;
    Box2& operator=(Box2&& other) = default;
    //...
};

Weitere Informationen finden Sie unter Explizit standardmäßige und gelöschte Funktionen.

constexpr-Konstruktoren

Ein Konstruktor kann als constexpr deklariert werden, wenn

  • sie wird entweder als Standard deklariert oder erfüllt sonst alle Bedingungen für constexpr-Funktionen im Allgemeinen;
  • die Klasse hat keine virtuellen Basisklassen;
  • jeder parameter ist ein Literaltyp;
  • Der Textkörper ist kein Funktions-Try-Block;
  • alle nicht statischen Datenmmber und Basisklassenunterobjekte werden initialisiert;
  • wenn die Klasse (a) eine Gewerkschaft mit Variantenmitgliedern ist oder (b) anonyme Gewerkschaften hat, wird nur einer der Gewerkschaftsmitglieder initialisiert;
  • Jedes nicht statische Datenelement des Klassentyps und alle Unterobjekte der Basisklasse weisen einen Constexpr-Konstruktor auf.

Initialisierungslistenkonstruktoren

Wenn ein Konstruktor einen std::initializer_list<T> Parameter als Parameter verwendet, und alle anderen Parameter über Standardargumente verfügen, wird dieser Konstruktor in der Überladungsauflösung ausgewählt, wenn die Klasse über die direkte Initialisierung instanziiert wird. Sie können die initializer_list verwenden, um jedes Element zu initialisieren, das es akzeptieren kann. Angenommen, die Box-Klasse (siehe vorherige Darstellung) hat ein std::vector<string> Element m_contents. Sie können einen Konstruktor wie folgt bereitstellen:

    Box(initializer_list<string> list, int w = 0, int h = 0, int l = 0)
        : m_contents(list), m_width(w), m_height(h), m_length(l)
{}

Erstellen Sie dann Box-Objekte wie folgt:

    Box b{ "apples", "oranges", "pears" }; // or ...
    Box b2(initializer_list<string> { "bread", "cheese", "wine" }, 2, 4, 6);

Explizite Konstruktoren

Wenn eine Klasse einen Konstruktor mit einem einzelnen Parameter aufweist oder wenn alle Parameter mit Ausnahmen von einem einen Standardwert besitzen, kann der Parametertyp impliziert zum Klassentyp konvertiert werden. Beispielsweise wenn die Box-Klasse über einen derartigen Konstruktor verfügt:

Box(int size): m_width(size), m_length(size), m_height(size){}

Es ist möglich, ein Box wie folgt zu initialisieren:

Box b = 42;

Alternativ können Sie eine Ganzzahl an eine Funktion weiterleiten, die ein Feld verwendet:

class ShippingOrder
{
public:
    ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage){}

private:
    Box m_box;
    double m_postage;
}
//elsewhere...
    ShippingOrder so(42, 10.8);

Derartige Konvertierungen können in einigen Fällen hilfreich sein. Häufiger führen sie jedoch möglicherweise zu feinen, aber schwerwiegenden Fehlern in Ihrem Code. Im Allgemeinen sollten Sie die explicit Schlüsselwort (keyword) für einen Konstruktor (und benutzerdefinierte Operatoren) verwenden, um diese Art impliziter Typkonvertierung zu verhindern:

explicit Box(int size): m_width(size), m_length(size), m_height(size){}

Wenn der Konstruktor explizit ist, verursacht diese Zeile einen Compilerfehler: ShippingOrder so(42, 10.8);. Weitere Informationen finden Sie unter Benutzerdefinierte Typkonvertierungen.

Bauauftrag

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 Basisklassenmber in der Reihenfolge initialisiert, in der sie in der Klassendeklaration angezeigt werden. Schließlich wird der abgeleitete Konstruktor aufgerufen.

#include <iostream>

using namespace std;

class Contained1 {
public:
    Contained1() { cout << "Contained1 ctor\n"; }
};

class Contained2 {
public:
    Contained2() { cout << "Contained2 ctor\n"; }
};

class Contained3 {
public:
    Contained3() { cout << "Contained3 ctor\n"; }
};

class BaseContainer {
public:
    BaseContainer() { cout << "BaseContainer ctor\n"; }
private:
    Contained1 c1;
    Contained2 c2;
};

class DerivedContainer : public BaseContainer {
public:
    DerivedContainer() : BaseContainer() { cout << "DerivedContainer ctor\n"; }
private:
    Contained3 c3;
};

int main() {
    DerivedContainer dc;
}

So sieht die Ausgabe aus:

Contained1 ctor
Contained2 ctor
BaseContainer ctor
Contained3 ctor
DerivedContainer ctor

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, z. B. wenn ClassA sie von ClassB, der abgeleitet wird, der ClassCClassC Konstruktor zuerst aufgerufen wird, dann der ClassB Konstruktor, dann der ClassA Konstruktor.

Wenn eine Basisklasse keinen Standardkonstruktor aufweist, müssen Sie die Parameter des Basisklassenkonstruktors 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);
}

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 delegiert wird, werden alle vollständig konstruierten Basisklassenobjekte und -member zerstört. Da das Objekt selbst jedoch nicht vollständig konstruiert ist, wird der Destruktor nicht ausgeführt.

Abgeleitete Konstruktoren und erweiterte Aggregatinitialisierung

Wenn der Konstruktor einer Basisklasse nicht öffentlich ist, aber für eine abgeleitete Klasse zugänglich ist, können Sie keine leeren geschweiften Klammern verwenden, um ein Objekt des abgeleiteten Typs im /std:c++17 Modus und höher in Visual Studio 2017 und höher zu initialisieren.

Im folgenden Beispiel ist das konforme Verhalten von C++14 dargestellt:

struct Derived;

struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {};

Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // OK in C++14: Calls Derived::Derived()
               // which can call Base ctor.

In C++17 gilt Derived nun als Aggregattyp. Das bedeutet, dass die Initialisierung von Base über den privaten Standardkonstruktor direkt als Teil der erweiterten Aggregatinitialisierungsregel erfolgt. Zuvor wurde der Base private Konstruktor über den Derived Konstruktor aufgerufen und aufgrund der friend Deklaration erfolgreich.

Das folgende Beispiel zeigt das C++17-Verhalten in Visual Studio 2017 und höher im /std:c++17 Modus:

struct Derived;

struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {
    Derived() {} // add user-defined constructor
                 // to call with {} initialization
};

Derived d1; // OK. No aggregate init involved.

Derived d2 {}; // error C2248: 'Base::Base': can't access
               // private member declared in class 'Base'

Konstruktoren für Klassen mit mehreren Vererbungen

Wenn eine Klasse von mehreren Basisklassen abgeleitet wird, werden die Basisklassenkonstruktoren in der Reihenfolge aufgerufen, in der sie in der Deklaration der abgeleiteten Klasse aufgeführt sind:

#include <iostream>
using namespace std;

class BaseClass1 {
public:
    BaseClass1() { cout << "BaseClass1 ctor\n"; }
};
class BaseClass2 {
public:
    BaseClass2() { cout << "BaseClass2 ctor\n"; }
};
class BaseClass3 {
public:
    BaseClass3() { cout << "BaseClass3 ctor\n"; }
};
class DerivedClass : public BaseClass1,
                     public BaseClass2,
                     public BaseClass3
                     {
public:
    DerivedClass() { cout << "DerivedClass ctor\n"; }
};

int main() {
    DerivedClass dc;
}

Die folgende Ausgabe sollte angezeigt werden:

BaseClass1 ctor
BaseClass2 ctor
BaseClass3 ctor
DerivedClass ctor

Delegierende Konstruktoren

Ein delegierender Konstruktor ruft einen anderen Konstruktor in derselben Klasse auf, um einige der Schritte der Initialisierung zu erledigen. Dieses Feature ist nützlich, wenn Sie über mehrere Konstruktoren verfügen, die alle ähnliche Aufgaben ausführen müssen. Sie können die Standard-Logik in einem Konstruktor schreiben und von anderen aufrufen. Im folgenden trivialen Beispiel delegiert Box(int) seine Arbeit an Box(int,int,int):

class Box {
public:
    // Default constructor
    Box() {}

    // Initialize a Box with equal dimensions (i.e. a cube)
    Box(int i) :  Box(i, i, i)  // delegating constructor
    {}

    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}
    //... rest of class as before
};

Das von den Konstruktoren erstellte Objekt wird vollständig initialisiert, sobald jeder Konstruktor abgeschlossen ist. Weitere Informationen finden Sie unter Delegieren von Konstruktoren.

Erben von Konstruktoren (C++11)

Eine abgeleitete Klasse kann die Konstruktoren von einer direkten Basisklasse erben, indem eine using Deklaration verwendet wird, wie im folgenden Beispiel gezeigt:

#include <iostream>
using namespace std;

class Base
{
public:
    Base() { cout << "Base()" << endl; }
    Base(const Base& other) { cout << "Base(Base&)" << endl; }
    explicit Base(int i) : num(i) { cout << "Base(int)" << endl; }
    explicit Base(char c) : letter(c) { cout << "Base(char)" << endl; }

private:
    int num;
    char letter;
};

class Derived : Base
{
public:
    // Inherit all constructors from Base
    using Base::Base;

private:
    // Can't initialize newMember from Base constructors.
    int newMember{ 0 };
};

int main()
{
    cout << "Derived d1(5) calls: ";
    Derived d1(5);
    cout << "Derived d1('c') calls: ";
    Derived d2('c');
    cout << "Derived d3 = d2 calls: " ;
    Derived d3 = d2;
    cout << "Derived d4 calls: ";
    Derived d4;
}

/* Output:
Derived d1(5) calls: Base(int)
Derived d1('c') calls: Base(char)
Derived d3 = d2 calls: Base(Base&)
Derived d4 calls: Base()*/

Visual Studio 2017 und höher: Die using Anweisung im /std:c++17 Modus und höher bringt alle Konstruktoren aus der Basisklasse in den Bereich mit Ausnahme von Konstruktoren, die eine identische Signatur mit Konstruktoren in der abgeleiteten Klasse aufweisen. Im Allgemeinen empfiehlt es sich, vererbte Konstruktoren zu verwenden, wenn die abgeleitete Klasse keine neuen Datenmber oder Konstruktoren deklariert.

Eine Klassenvorlage kann alle Konstruktoren aus einem Typargument erben, wenn dieser Typ eine Basisklasse angibt:

template< typename T >
class Derived : T {
    using T::T;   // declare the constructors from T
    // ...
};

Eine ableitende Klasse kann nicht von mehreren Basisklassen erben, wenn diese Basisklassen Konstruktoren haben, die über eine identische Signatur verfügen.

Konstruktoren und zusammengesetzte Klassen

Klassen, die Klassenmmber 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"});
}

In diesem Abschnitt

Siehe auch

Klassen und Strukturen