Udostępnij za pośrednictwem


Konstruktory (C++)

Konstruktor jest specjalną funkcją elementu członkowskiego, która inicjuje wystąpienie jego klasy.Aby wywołać konstruktor, należy użyć nazwy klasy wraz z parametrami otoczonymi nawiasami zwykłymi lub klamrowymi.

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 });
}

Aby uzyskać więcej informacji, zobacz Reguły deklarowania konstruktorów.Informacji o inicjowania, zobacz Inicjalizacja.

Kolejność konstrukcji

Konstruktor wykonuje pracę w następującej kolejności:

  1. Wywołuje klasę podstawową i konstruktory elementów członkowskich w kolejności deklaracji.

  2. Jeśli klasa jest pochodną wirtualnych klas bazowych, inicjuje wskaźniki wirtualnej bazy obiektu.

  3. Jeśli klasa ma lub dziedziczy funkcje wirtualne, inicjuje wskaźniki funkcji wirtualnych obiektu.Wskaźniki funkcji wirtualnych wskazują na tabelę funkcji wirtualnych tej klasy, aby umożliwić poprawne powiązanie wywołań funkcji wirtualnych z kodem.

  4. Wykonuje każdy kod w jego treści funkcji.

Poniższy przykład pokazuje kolejność, w której konstruktory klasy podstawowej i elementów członkowskich są wywoływane w konstruktorze klasy pochodnej.Najpierw jest wywoływany podstawowy konstruktor, następnie elementy członkowskie klas bazowych są inicjowane w kolejności, w której pojawiają się w deklaracji klasy, a na koniec jest wywoływany pochodny konstruktor.

#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;
}

Oto dane wyjściowe:

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

Jeśli konstruktor zgłasza wyjątek, kolejność zniszczenia jest odwrotna do kolejności konstrukcji:

  1. Kod w treści funkcji konstruktora jest rozwinięty.

  2. Klasa podstawowa i elementy członkowskie są usuwane w kolejności odwrotnej do deklarowania.

  3. Jeśli konstruktor nie jest delegujący, wszystkie w pełni skonstruowane obiekty i elementy członkowskie klasy podstawowej zostaną zniszczone.Jednak ponieważ sam obiekt nie jest w pełni skonstruowany, destruktor nie zostanie uruchomiony.

Konstruktory jawne

Przy użyciu explicit słowo kluczowe w odniesieniu do konstruktora może uniemożliwić niejawne konwersje typów, jeśli tylko jeden parametr ma konstruktora lub wszystkie parametry z wyjątkiem jednej ma wartości domyślnej.Aby uzyskać więcej informacji, zobacz Konstruktory (C++).

Domyślne konstruktory

Domyślne konstruktory — to znaczy konstruktory, które nie mają parametrów — podlegają nieco innym regułom.

Jeśli w klasie nie zadeklarowano konstruktorów, kompilator dostarcza konstruktor domyślny:

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

int main(){

    Box box1{};
    Box box2;
}

Gdy wywołujesz konstruktor domyślny i próbujesz użyć nawiasów, wyświetla się ostrzeżenie:

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

Jest to przykład problemu najbardziej irytującej interpretacji składni (Most Vexing Parse).Ponieważ wyrażenie przykładowe może być interpretowane jako deklaracja funkcji lub wywołanie konstruktora domyślnego i ponieważ analizatory składni języka C++ promują deklaracje w stosunku do innych obiektów, wyrażenie jest traktowane jako deklaracja funkcji.Aby uzyskać więcej informacji, zobacz najbardziej Vexing analizy.

Jeśli zadeklarowano jakiekolwiek konstruktory inne niż domyślne, kompilator nie udostępnia domyślnego konstruktora:

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
}

Jeśli klasa nie ma domyślnego konstruktora, nie można utworzyć tablicy obiektów tej klasy przy użyciu wyłącznie składni z nawiasami kwadratowymi.Na przykład, biorąc pod uwagę poprzedni blok kodu, tablicy pól nie można zadeklarować w ten sposób:

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

Możesz jednak użyć zestawu list inicjatorów do zainicjowania tablicy pól:

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

Kopiowanie i przenoszenie konstruktorów

Element Kopiuj konstruktora używa odwołanie do obiektu, aby jego kopię.Jeśli nie podasz konstruktora kopiującego dla klasy, kompilator utworzy domyślny, nawet jeśli zadeklarowano operację kopiowania, operację przenoszenia lub destruktor.Aby uzyskać więcej informacji, zobacz Reguły deklarowania konstruktorów.

Element Przenieś konstruktora umożliwia transfer alokacji pamięci z jednego obiektu do drugiego.Aby uzyskać więcej informacji, zobacz Porady: zapisywanie konstruktora przenoszenia.

Konstruktory jawnie ustawiane domyślnie i usunięte

Jawnie domyślnych konstruktorów kopii, domyślnych konstruktorów, operatory przypisania kopiowania i destruktory, ale jawnego domyślne konstruktorów przenoszenia i operatory przypisania przenoszenia nie jest obsługiwany.(Ta obsługa znajduje się w programie Visual Studio 2015). Możesz jawnie usunąć wszystkie funkcje specjalne.Aby uzyskać więcej informacji, zobacz Jawnie domyślne i usunięte funkcje.

Konstruktory klas pochodnych

Konstruktor klasy pochodnej zawsze wywołuje konstruktora klasy podstawowej, tak aby mógł polegać na całkowicie skonstruowanych podstawowych klasach, zanim wszelkie dodatkowe prace zostaną wykonane.Konstruktory klasy podstawowej są wywoływane w kolejności wyprowadzenia — na przykład, jeśli ClassA jest wyprowadzany z ClassB, który z kolei jest wyprowadzany z ClassC, konstruktor ClassC jest wywoływany jako pierwszy, następnie konstruktor ClassB, a na koniec konstruktor ClassA.

Jeśli klasa podstawowa nie ma domyślnego konstruktora, musisz podać parametry konstruktora klasy podstawowej w konstruktorze klasy pochodnej:

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);
} 

Konstruktory dla klas, które mają wiele dziedziczeń

Jeśli klasa jest pochodną wielu klas podstawowych, konstruktory klas podstawowych są wywoływane w kolejności, w której są wymienione w deklaracji klasy pochodnej:

#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;
}

Powinny się wyświetlić poniższe dane wyjściowe:

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

Funkcje wirtualne w konstruktorach

Zalecamy, aby uważać podczas wywołania funkcji wirtualnych w konstruktorach.Ponieważ konstruktor klasy podstawowej zawsze jest wywoływany przed konstruktorem klasy pochodnej, funkcja, która jest wywołana w konstruktorze klasy podstawowej, jest wersją klasy podstawowej, a nie wersją klasy pochodnej.W poniższym przykładzie konstruowania DerivedClass powoduje, że BaseClass wykonania print_it() do wykonania przed DerivedClass przyczyny konstruktora DerivedClass wykonania print_it() do wykonania:

#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;
}

Oto dane wyjściowe:

BaseClass print_it
Derived Class print_it

Konstruktory i klasy złożone

Klasy, które zawierają typ klasy elementów członkowskich są znane jako złożony klasach.Gdy jest tworzony element członkowski typu klasa klasy złożonej, konstruktor jest wywoływany przed konstruktorem tej klasy.Gdy klasa zamknięta nie ma domyślnego konstruktora, należy użyć listy inicjalizacji w konstruktorze klasy złożonej.W wcześniej StorageBox przykład, jeśli zmienisz typ m_label Zmienna członka do nowego Label klasy, należy wywołać konstruktora klasy podstawowej i zainicjować m_label zmiennej w StorageBox konstruktora:

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"});
}

Delegowanie konstruktorów

Element Delegowanie konstruktora wywołuje konstruktor różnych w tej samej klasy wykonania określonej pracy inicjowania.W poniższym przykładzie klasa pochodna ma trzy konstruktory — drugi konstruktor deleguje do pierwszego, a trzeci konstruktor deleguje do drugiego:

#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);
}

Oto dane wyjściowe:

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

Obiekt utworzony przez konstruktory jest w pełni zainicjowany natychmiast po zakończeniu wszelkich konstruktorów.DerivedContainer(int int1) zakończy się powodzeniem, ale DerivedContainer(int int1, int int2) zakończy się niepowodzeniem i destruktora jest wywoływana.

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){
    }
}

Dane wyjściowe:

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

Aby uzyskać więcej informacji, zobacz Jednolite inicjowanie i delegowanie konstruktorów.

Zobacz też

Informacje

Specjalne funkcje członkowskie (C++)