W poprzedniej lekcji na temat podstawowego dziedziczenia w C++ dowiedziałeś się, że klasy mogą dziedziczyć elementy i funkcje z innych klas. Podczas tej lekcji przyjrzymy się bliżej kolejności konstrukcji zachodzącej podczas tworzenia instancji klasy pochodnej.
Najpierw wprowadźmy kilka nowych klas, które pomogą nam zilustrować kilka ważnych punktów.
class Base
{
public:
int m_id {};
Base(int id=0)
: m_id { id }
{
}
int getId() const { return m_id; }
};
class Derived: public Base
{
public:
double m_cost {};
Derived(double cost=0.0)
: m_cost { cost }
{
}
double getCost() const { return m_cost; }
};W tym przykładzie klasa Derived wywodzi się z klasy Base.

Ponieważ Derived dziedziczy funkcje i zmienne z Base, możesz założyć, że elementy Base są kopiowane do Pochodne. Jednak nie jest to prawdą. Zamiast tego możemy uznać Derived za klasę składającą się z dwóch części: jednej części Derived i jednej części Base.

Widzieliście już wiele przykładów tego, co się dzieje, gdy tworzymy instancję normalnej (niepochodnej) klasy:
int main()
{
Base base;
return 0;
}Base jest klasą niepochodną, ponieważ nie dziedziczy z żadnej innej klasy. C++ przydziela pamięć dla Base, a następnie wywołuje domyślny konstruktor Base w celu wykonania inicjalizacji.
Przyjrzyjmy się teraz, co się dzieje, gdy tworzymy instancję klasy pochodnej:
int main()
{
Derived derived;
return 0;
}Gdybyś sam tego spróbował, nie zauważyłbyś żadnej różnicy w stosunku do poprzedniego przykładu, w którym tworzymy instancję klasy niepochodnej Base. Ale za kulisami wszystko dzieje się nieco inaczej. Jak wspomniano powyżej, Derived składa się tak naprawdę z dwóch części: części bazowej i części pochodnej. Kiedy C++ konstruuje obiekty pochodne, robi to etapami. Najpierw konstruowana jest klasa najbardziej podstawowa (na szczycie drzewa dziedziczenia). Następnie każda klasa potomna jest konstruowana w odpowiedniej kolejności, aż do ostatniej zbudowanej klasy potomnej (na dole drzewa dziedziczenia).
Więc kiedy tworzymy instancję Derived, najpierw tworzona jest podstawowa część Derived (przy użyciu domyślnego konstruktora Base). Po zakończeniu części Base tworzona jest część Derived (przy użyciu domyślnego konstruktora Derived). W tym momencie nie ma już więcej klas pochodnych, więc skończyliśmy.
Ten proces jest w zasadzie łatwy do zilustrowania.
#include <iostream>
class Base
{
public:
int m_id {};
Base(int id=0)
: m_id { id }
{
std::cout << "Base\n";
}
int getId() const { return m_id; }
};
class Derived: public Base
{
public:
double m_cost {};
Derived(double cost=0.0)
: m_cost { cost }
{
std::cout << "Derived\n";
}
double getCost() const { return m_cost; }
};
int main()
{
std::cout << "Instantiating Base\n";
Base base;
std::cout << "Instantiating Derived\n";
Derived derived;
return 0;
}Ten program daje następujący wynik:
Instantiating Base Base Instantiating Derived Base Derived
Jak widzisz, kiedy konstruowaliśmy Derived, najpierw skonstruowana została podstawowa część Derived. Ma to sens: logicznie rzecz biorąc, dziecko nie może istnieć bez rodzica. Jest to także bezpieczny sposób działania: klasa podrzędna często używa zmiennych i funkcji pochodzących od rodzica, ale klasa nadrzędna nic nie wie o dziecku. Utworzenie instancji klasy nadrzędnej gwarantuje, że zmienne te zostaną już zainicjowane w momencie utworzenia klasy pochodnej i będą gotowe do użycia.
Kolejność konstrukcji łańcuchów dziedziczenia
Czasami zdarza się, że klasy wywodzą się z innych klas, które same wywodzą się z innych klas. Na przykład:
#include <iostream>
class A
{
public:
A()
{
std::cout << "A\n";
}
};
class B: public A
{
public:
B()
{
std::cout << "B\n";
}
};
class C: public B
{
public:
C()
{
std::cout << "C\n";
}
};
class D: public C
{
public:
D()
{
std::cout << "D\n";
}
};Pamiętaj, że C++ zawsze najpierw konstruuje „pierwszą” lub „najbardziej podstawową” klasę. Następnie przechodzi przez drzewo dziedziczenia w odpowiedniej kolejności i konstruuje każdą kolejną klasę pochodną.
Oto krótki program ilustrujący kolejność tworzenia w całym łańcuchu dziedziczenia.
int main()
{
std::cout << "Constructing A: \n";
A a;
std::cout << "Constructing B: \n";
B b;
std::cout << "Constructing C: \n";
C c;
std::cout << "Constructing D: \n";
D d;
}Kod ten wypisuje następujący komunikat:
Constructing A: A Constructing B: A B Constructing C: A B C Constructing D: A B C D
Wnioski
C++ konstruuje klasy pochodne etapami, zaczynając od klasy najbardziej podstawowej (na górze drzewa dziedziczenia) i kończąc na klasie najbardziej potomnej (w dół drzewa dziedziczenia). Podczas konstruowania każdej klasy wywoływany jest odpowiedni konstruktor z tej klasy w celu zainicjowania tej części klasy.
Zauważysz, że wszystkie nasze przykładowe klasy w tej sekcji mają używane konstruktory domyślne klasy bazowej (dla uproszczenia). W następnej lekcji przyjrzymy się bliżej roli konstruktorów w procesie konstruowania klas pochodnych (w tym temu, jak jawnie wybrać konstruktor klasy bazowej, którego ma używać klasa pochodna).

