Abgeleitete Klassen
Jeder Klassentyp (egal ob deklariert mit class-key class oder struct) kann als abgeleitet von einer oder mehreren Basisklassen deklariert werden, die ihrerseits von ihren eigenen Basisklassen abgeleitet sein können, wodurch eine Vererbungshierarchie entsteht.
Inhalt |
[bearbeiten] Syntax
Die Liste der Basisklassen wird in der base-clause der Klassendeklarationssyntax angegeben. Die base-clause besteht aus dem Zeichen : gefolgt von einer durch Kommata getrennten Liste von einem oder mehreren base-specifiern.
| attr (optional) class-or-computed | (1) | ||||||||
attr (optional) virtual class-or-computed |
(2) | ||||||||
| attr (optional) access-specifier class-or-computed | (3) | ||||||||
attr (optional) virtual access-specifier class-or-computed |
(4) | ||||||||
attr (optional) access-specifier virtual class-or-computed |
(5) | ||||||||
virtual und access-specifier können in beliebiger Reihenfolge erscheinen.| attr | - | (seit C++11) Sequenz einer beliebigen Anzahl von Attributen | ||||
| access-specifier | - | einer von private, public oder protected | ||||
| class-or-computed | - | eine von
|
Ein elaborierter Typspezifizierer kann aufgrund von Syntaxbeschränkungen nicht direkt als class-or-computed erscheinen.
|
base-specifiers in einer base-clause können Parameter Pack Expansions sein. Eine Klasse oder Struktur, die |
(seit C++11) |
Wenn access-specifier weggelassen wird, ist er standardmäßig public für abgeleitete Klassen, die mit class-key struct deklariert werden, und private für abgeleitete Klassen, die mit class-key class deklariert werden.
struct Base { int a, b, c; }; // every object of type Derived includes Base as a subobject struct Derived : Base { int b; }; // every object of type Derived2 includes Derived and Base as subobjects struct Derived2 : Derived { int c; };
Klassen, die durch class-or-computed bezeichnet werden und in der base-clause aufgeführt sind, sind direkte Basisklassen. Ihre Basen sind indirekte Basisklassen. Dieselbe Klasse kann nicht mehr als einmal als direkte Basisklasse angegeben werden, aber dieselbe Klasse kann sowohl direkte als auch indirekte Basisklasse sein.
Jede direkte und indirekte Basisklasse ist als Basisklassen-Subobjekt innerhalb der Objekt-Repräsentation der abgeleiteten Klasse mit einem ABI-abhängigen Offset vorhanden. Leere Basisklassen erhöhen normalerweise nicht die Größe des abgeleiteten Objekts aufgrund der Empty Base Optimization. Die Konstruktoren von Basisklassen-Subobjekten werden vom Konstruktor der abgeleiteten Klasse aufgerufen: Argumente können diesen Konstruktoren in der Member-Initialisierungsliste übergeben werden.
[bearbeiten] Virtuelle Basisklassen
Für jede einzelne Basisklasse, die virtual angegeben ist, enthält das am weitesten abgeleitete Objekt nur ein Basisklassen-Subobjekt dieses Typs, auch wenn die Klasse mehrmals in der Vererbungshierarchie vorkommt (solange sie jedes Mal virtual geerbt wird).
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // every object of type AA has one X, one Y, one Z, and two B's: // one that is the base of Z and one that is shared by X and Y struct AA : X, Y, Z { AA() { X::n = 1; // modifies the virtual B subobject's member Y::n = 2; // modifies the same virtual B subobject's member Z::n = 3; // modifies the non-virtual B subobject's member std::cout << X::n << Y::n << Z::n << '\n'; // prints 223 } };
Ein Beispiel für eine Vererbungshierarchie mit virtuellen Basisklassen ist die iostream-Hierarchie der Standardbibliothek: std::istream und std::ostream sind von std::ios abgeleitet, wobei virtuelle Vererbung verwendet wird. std::iostream ist von sowohl std::istream als auch std::ostream abgeleitet, so dass jede Instanz von std::iostream ein std::ostream-Subobjekt, ein std::istream-Subobjekt und nur ein std::ios-Subobjekt (und folglich ein std::ios_base) enthält.
Alle virtuellen Basis-Subobjekte werden vor allen nicht-virtuellen Basis-Subobjekten initialisiert, sodass nur die am weitesten abgeleitete Klasse die Konstruktoren der virtuellen Basen in ihrer Member-Initialisierungsliste aufruft.
struct B { int n; B(int x) : n(x) {} }; struct X : virtual B { X() : B(1) {} }; struct Y : virtual B { Y() : B(2) {} }; struct AA : X, Y { AA() : B(3), X(), Y() {} }; // the default constructor of AA calls the default constructors of X and Y // but those constructors do not call the constructor of B because B is a virtual base AA a; // a.n == 3 // the default constructor of X calls the constructor of B X x; // x.n == 1
Es gibt spezielle Regeln für die nicht qualifizierte Namenssuche für Klassenmember, wenn virtuelle Vererbung beteiligt ist (manchmal als Dominanzregeln bezeichnet).
[bearbeiten] Öffentliche Vererbung
Wenn eine Klasse public als Member-Zugriffsspezifizierer verwendet, um von einer Basisklasse abzuleiten, sind alle öffentlichen Member der Basisklasse als öffentliche Member der abgeleiteten Klasse zugänglich, und alle geschützten Member der Basisklasse sind als geschützte Member der abgeleiteten Klasse zugänglich (private Member der Basis sind nie zugänglich, es sei denn, sie sind als Friend deklariert).
Öffentliche Vererbung modelliert die Subtyp-Beziehung der objektorientierten Programmierung: Das Objekt der abgeleiteten Klasse IST-EIN Objekt der Basisklasse. Referenzen und Zeiger auf ein abgeleitetes Objekt sollen von jedem Code, der Referenzen oder Zeiger auf eine seiner öffentlichen Basen erwartet, verwendbar sein (siehe LSP) oder, in DbC-Begriffen, eine abgeleitete Klasse sollte Klasseninvarianten ihrer öffentlichen Basen beibehalten, keine Vorbedingung stärken oder eine Nachbedingung einer Member-Funktion schwächen, die sie überschreibt.
#include <iostream> #include <string> #include <vector> struct MenuOption { std::string title; }; // Menu is a vector of MenuOption: options can be inserted, removed, reordered... // and has a title. class Menu : public std::vector<MenuOption> { public: std::string title; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) std::cout << " " << (i + 1) << ". " << at(i).title << '\n'; } }; // Note: Menu::title is not problematic because its role is independent of the base class. enum class Color { WHITE, RED, BLUE, GREEN }; void apply_terminal_color(Color) { /* OS-specific */ } // THIS IS BAD! // ColorMenu is a Menu where every option has a custom color. class ColorMenu : public Menu { public: std::vector<Color> colors; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) { std::cout << " " << (i + 1) << ". "; apply_terminal_color(colors[i]); std::cout << at(i).title << '\n'; apply_terminal_color(Color::WHITE); } } }; // ColorMenu needs the following invariants that cannot be satisfied // by publicly inheriting from Menu, for example: // - ColorMenu::colors and Menu must have the same number of elements // - To make sense, calling erase() should remove also elements from colors, // in order to let options keep their colors // Basically every non-const call to a std::vector method will break the invariant // of the ColorMenu and will need fixing from the user by correctly managing colors. int main() { ColorMenu color_menu; // The big problem of this class is that we must keep ColorMenu::Color // in sync with Menu. color_menu.push_back(MenuOption{"Some choice"}); // color_menu.print(); // ERROR! colors[i] in print() is out of range color_menu.colors.push_back(Color::RED); color_menu.print(); // OK: colors and Menu has the same number of elements }
[bearbeiten] Geschützte Vererbung
Wenn eine Klasse protected als Member-Zugriffsspezifizierer verwendet, um von einer Basisklasse abzuleiten, sind alle öffentlichen und geschützten Member der Basisklasse als geschützte Member der abgeleiteten Klasse zugänglich (private Member der Basis sind nie zugänglich, es sei denn, sie sind als Friend deklariert).
Geschützte Vererbung kann für "kontrollierte Polymorphie" verwendet werden: Innerhalb der Member von Derived, sowie innerhalb der Member aller weiteren abgeleiteten Klassen, IST Derived-EIN Basis: Referenzen und Zeiger auf Derived können dort verwendet werden, wo Referenzen und Zeiger auf Base erwartet werden.
[bearbeiten] Private Vererbung
Wenn eine Klasse private als Member-Zugriffsspezifizierer verwendet, um von einer Basisklasse abzuleiten, sind alle öffentlichen und geschützten Member der Basisklasse als private Member der abgeleiteten Klasse zugänglich (private Member der Basis sind nie zugänglich, es sei denn, sie sind als Friend deklariert).
Private Vererbung wird häufig im Policy-Based Design verwendet, da Policies normalerweise leere Klassen sind, und ihre Verwendung als Basis sowohl statische Polymorphie ermöglicht als auch die Empty Base Optimization nutzt.
Private Vererbung kann auch verwendet werden, um die Kompositionsbeziehung zu implementieren (das Basisklassen-Subobjekt ist ein Implementierungsdetail des abgeleiteten Klassenobjekts). Die Verwendung eines Members bietet eine bessere Kapselung und wird im Allgemeinen bevorzugt, es sei denn, die abgeleitete Klasse benötigt Zugriff auf geschützte Member (einschließlich Konstruktoren) der Basis, muss einen virtuellen Member der Basis überschreiben, benötigt, dass die Basis vor einigen anderen Basis-Subobjekten konstruiert und danach zerstört wird, muss eine virtuelle Basis teilen oder muss die Konstruktion einer virtuellen Basis steuern. Die Verwendung von Membern zur Implementierung von Komposition ist auch nicht anwendbar bei Mehrfachvererbung von einem Parameter Pack oder wenn die Identitäten der Basisklassen zur Kompilierzeit durch Template-Metaprogrammierung bestimmt werden.
Ähnlich wie bei der geschützten Vererbung kann auch die private Vererbung für kontrollierte Polymorphie verwendet werden: Innerhalb der Member der abgeleiteten Klasse (aber nicht innerhalb weiterer abgeleiteter Klassen) IST abgeleitet-EIN Basis.
template<typename Transport> class service : private Transport // private inheritance from the Transport policy { public: void transmit() { this->send(...); // send using whatever transport was supplied } }; // TCP transport policy class tcp { public: void send(...); }; // UDP transport policy class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // send over TCP
[bearbeiten] Namenssuche für Member
Regeln für die nicht qualifizierte und qualifizierte Namenssuche für Klassenmember sind detailliert in Namenssuche.
[bearbeiten] Schlüsselwörter
[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 1710 | C++98 | die Syntax von class-or-decltype machte es unmöglich, von einer abhängigen Klasse abzuleiten, bei der der template-Disambiguator erforderlich ist |
erlaubt template |