Mehrere Basisklassen
Eine Klasse kann von mehreren Basisklassen abgeleitet werden. In einem Modell mit mehreren Vererbungen (bei dem Klassen aus mehreren Basisklassen abgeleitet werden), werden die Basisklassen mithilfe des Grammatikelements für die Basisliste angegeben. Beispielsweise kann die Klassendeklaration für CollectionOfBook
, abgeleitet von Collection
und Book
, angegeben werden:
// deriv_MultipleBaseClasses.cpp
// compile with: /LD
class Collection {
};
class Book {};
class CollectionOfBook : public Book, public Collection {
// New members
};
Die Reihenfolge, in der Basisklassen angegeben werden, ist nicht signifikant, außer in bestimmten Fällen, in denen Konstruktoren und Destruktoren aufgerufen werden. In diesen Fällen wirkt sich die Reihenfolge, in der Basisklassen angegeben werden, auf Folgendes aus:
Die Reihenfolge, in der Konstruktoren aufgerufen werden. Wenn Ihr Code darauf basiert, dass der
Book
-Teil vonCollectionOfBook
vor demCollection
-Teil initialisiert wird, ist die Reihenfolge der Spezifikation signifikant. Initialisierung erfolgt in der Reihenfolge, in der die Klassen in der Basisliste angegeben werden.Die Reihenfolge, in der Destruktoren zur Bereinigung aufgerufen werden. Auch wenn ein bestimmter „Teil“ der Klasse vorhanden sein muss, wenn der andere Teil zerstört wird, ist die Reihenfolge relevant. Destruktoren werden in umgekehrter Reihenfolge der klassen aufgerufen, die in der Basisliste angegeben sind.
Hinweis
Die Reihenfolge der Spezifikation der Basisklassen kann das Speicherlayout der Klasse beeinflussen. Treffen Sie keine Programmierentscheidungen, die auf der Reihenfolge der Basismember im Arbeitsspeicher basieren.
Wenn Sie die Basisliste angeben, können Sie nicht mehr als einmal denselben Klassennamen angeben. Es ist jedoch möglich, dass eine Klasse eine indirekte Basis für eine abgeleitete Klasse mehr als einmal ist.
Virtuelle Basisklassen
Da eine Klasse mehrfach als indirekte Basisklasse für eine abgeleiteten Klasse auftreten kann, stellt C++ eine Methode bereit, um die Arbeitsweise solcher Basisklassen zu optimieren. Virtuelle Basisklassen bieten eine Möglichkeit, Speicherplatz zu sparen und Mehrdeutigkeiten in Klassenhierarchien zu vermeiden, die Mehrfachvererbung verwenden.
Jedes nicht virtuelle Objekt enthält eine Kopie der Datenmember, die in der Basisklasse definiert sind. Durch diese Duplizierung wird Speicherplatz verschwendet, und Sie müssen bei jedem Zugriff angeben, welche Kopie der Basisklassenmember Sie wünschen.
Wenn eine Basisklasse als virtuelle Basisklasse angegeben ist, kann sie, ohne Verdoppelung der Datenmember, mehrmals als indirekte Basisklasse fungieren. Eine einzelne Kopie der Datenmember wird von allen Basisklassen, die diese als virtuelle Basis verwenden, gemeinsam genutzt.
Beim Deklarieren einer virtuellen Basisklasse wird die virtual
Schlüsselwort (keyword) in den Basislisten der abgeleiteten Klassen angezeigt.
Betrachten Sie die Klassenhierarchie in der folgenden Abbildung, die eine simulierte Mittagspause veranschaulicht:
Die Basisklasse ist "Queue". Die Kassenwarteschlange und die Mittagswarteschlange erben beide von der Warteschlange. Schließlich erbt die Mittagskassenwarteschlange sowohl von der Kassierwarteschlange als auch von der Mittagswarteschlange.
Simuliertes Diagramm der Mittagspause
In der Abbildung ist Queue
die Basisklasse für CashierQueue
und LunchQueue
. Wenn jedoch beide Klassen kombiniert werden, um LunchCashierQueue
zu bilden, tritt folgendes Problem auf: Die neue Klasse enthält zwei Unterobjekte des Typs Queue
, eines von CashierQueue
und das andere von LunchQueue
. Die folgende Abbildung zeigt das konzeptionelle Speicherlayout (das tatsächliche Speicherlayout kann optimiert werden):
Die Abbildung zeigt ein Lunch Cashier Queue -Objekt mit zwei Unterobjekten darin: Cashier Queue und Lunch Queue. Sowohl die Kassenwarteschlange als auch die Mittagswarteschlange enthalten ein Unterobjekt "Warteschlangen"."
Simuliertes Mittagessen-Objekt
Es gibt zwei Queue
Unterobjekte im LunchCashierQueue
Objekt. Der folgende Code deklariert Queue
als virtuelle Basisklasse:
// deriv_VirtualBaseClasses.cpp
// compile with: /LD
class Queue {};
class CashierQueue : virtual public Queue {};
class LunchQueue : virtual public Queue {};
class LunchCashierQueue : public LunchQueue, public CashierQueue {};
Die virtual
Schlüsselwort (keyword) stellt sicher, dass nur eine Kopie des Unterobjekts Queue
enthalten ist (siehe folgende Abbildung).
Das Diagramm zeigt ein Lunch Cashier Queue -Objekt, das ein Unterobjekt der Cashierwarteschlange und ein Unterobjekt der Mittagswarteschlange enthält. Sowohl cashier Queue als auch Lunch Queue teilen dasselbe Unterobjekt der Warteschlange.
Simuliertes Lunchzeilenobjekt mit virtuellen Basisklassen
Eine Klasse kann eine virtuelle Komponente und eine nicht virtuelle Komponente eines bestimmten Typs haben. Dies geschieht in den Bedingungen, die in der folgenden Abbildung dargestellt sind:
Das Diagramm zeigt eine Warteschlangenbasisklasse. Eine Kassierwarteschlangenklasse und eine Mittagswarteschlangenklasse erben virtuell von der Warteschlange. Eine dritte Klasse, Takeout Queue, erbt nicht virtuell von der Warteschlange. Die Mittagskassenwarteschlange erbt sowohl von der Kassierwarteschlange als auch von der Mittagswarteschlange. Die Lunch Takeout Cashier Queue erbt sowohl von der Warteschlange für die Mittagskasse als auch von der Takeout-Warteschlange.
Virtuelle und nichtvirtuale Komponenten derselben Klasse
In der Abbildung verwenden CashierQueue
und LunchQueue
Queue
als virtuelle Basisklasse. Allerdings spezifiziert TakeoutQueue
Queue
als Basisklasse und nicht als virtuelle Basisklasse. Daher verfügt LunchTakeoutCashierQueue
über zwei Unterobjekte des Typs Queue
: eines aus dem Vererbungspfad, der LunchCashierQueue
einschließt, und eines aus dem Pfad, der TakeoutQueue
einschließt. Dies wird in der folgenden Abbildung verdeutlicht.
Ein Lunch Takeout Cashier Queue -Objekt wird angezeigt, das zwei Unterobjekte enthält: eine Takeout-Warteschlange (die ein Warteschlangenunterobjekt enthält) und eine Mittagskassenwarteschlange. Das Unterobjekt "Lunch Cashier Queue" enthält ein Unterobjekt "Cashier Queue" und ein Unterobjekt der Mittagswarteschlange, das beide ein Unterobjekt "Queue" gemeinsam verwenden.
Objektlayout mit virtueller und nichtvirtualer Vererbung
Hinweis
Virtuelle Vererbung bietet wesentliche Vorteile im Vergleich zur nicht virtuellen Vererbung. Allerdings kann es zu Mehraufwand bei der Verarbeitung kommen.
Wenn eine abgeleitete Klasse eine virtuelle Funktion außer Kraft setzt, die sie von einer virtuellen Basisklasse erbt, und wenn ein Konstruktor oder ein Destruktor für die abgeleitete Basisklasse diese Funktion mithilfe eines Zeigers auf die virtuelle Basisklasse aufruft, kann der Compiler andere ausgeblendete "vtordisp"-Felder in die Klassen mit virtuellen Basen einführen. Die /vd0
Compileroption unterdrückt das Hinzufügen des ausgeblendeten vtordisp-Konstruktors/Destruktor-Verdrängungselements. Die /vd1
Compileroption, der Standardwert, aktiviert sie, wo sie erforderlich sind. Deaktivieren Sie vtordisps nur, wenn Sie sicher sind, dass alle Klassenkonstruktoren und Destruktoren virtuelle Funktionen virtuell aufrufen.
Die /vd
Compileroption wirkt sich auf ein gesamtes Kompilierungsmodul aus. Verwenden Sie das vtordisp
Pragma, um Felder auf Klassenbasis zu unterdrücken und dann erneut vtordisp
zu aktivieren:
#pragma vtordisp( off )
class GetReal : virtual public { ... };
\#pragma vtordisp( on )
Mehrdeutigkeiten bei Namen
Mehrfachvererbung bietet die Möglichkeit, dass Namen in mehr als einem Pfad geerbt werden können. Die Klassenmemembenamen entlang dieser Pfade sind nicht unbedingt eindeutig. Diese Namenskonflikte werden als Mehrdeutigkeiten bezeichnet.
Jeder Ausdruck, der auf einen Klassenmember verweist, muss einen eindeutigen Verweis darstellen. Das folgende Beispiel zeigt, wie Mehrdeutigkeiten entstehen:
// deriv_NameAmbiguities.cpp
// compile with: /LD
// Declare two base classes, A and B.
class A {
public:
unsigned a;
unsigned b();
};
class B {
public:
unsigned a(); // class A also has a member "a"
int b(); // and a member "b".
char c;
};
// Define class C as derived from A and B.
class C : public A, public B {};
Angesichts der vorstehenden Klassendeklarationen ist Code wie das folgende mehrdeutig, da unklar ist, ob b
sich auf das b
In A
- oder In-Element B
bezieht:
C *pc = new C;
pc->b();
Betrachten Sie das vorhergehende Beispiel. Da es sich bei dem Namen a
um ein Element der Klasse A
und der Klasse B
handelt, kann der Compiler nicht erkennen, welche a
die funktion als aufgerufen werden soll. Zugriff auf einen Member ist mehrdeutig, wenn er auf mehr als eine Funktion, ein Objekt, einen Typ oder einen Enumerator verweisen kann.
Der Compiler erkennt Mehrdeutigkeiten, indem er Tests in dieser Reihenfolge durchführt:
Wenn der Zugriff auf den Namen mehrdeutig ist (wie gerade beschrieben) , wird eine Fehlermeldung generiert.
Wenn überladene Funktionen eindeutig sind, werden sie aufgelöst.
Wenn der Zugriff auf den Namen gegen Memberzugangsberechtigungen verstößt, wird eine Fehlermeldung generiert. (Weitere Informationen finden Sie unter Member-Access-Steuerung.)
Wenn ein Ausdruck durch Vererbung eine Mehrdeutigkeit erzeugt, können Sie dies manuell korrigieren, indem Sie den betreffenden Namen mit dem Klassennamen qualifizieren. Um das vorherige Beispiel ordnungsgemäß ohne Mehrdeutigkeiten kompilieren zu können, verwenden Sie beispielsweise folgenden Code:
C *pc = new C;
pc->B::a();
Hinweis
Wenn C
deklariert ist, können Fehler verursacht werden, wenn auf B
im Bereich von C
verwiesen wird. Es wird jedoch kein Fehler ausgegeben, bis ein nicht qualifizierter Verweis auf B
tatsächlich im Bereich von C
erfolgt.
Dominanz
Es ist möglich, dass mehrere Namen (Funktion, Objekt oder Enumerator) über ein Vererbungsdiagramm erreicht werden können. Solche Fälle unterscheiden sich nicht eindeutig von nicht virtuellen Basisklassen. Sie sind auch mehrdeutig mit virtuellen Basisklassen, es sei denn, einer der Namen "dominiert" die anderen.
Ein Name dominiert einen anderen Namen, wenn er in beiden Klassen definiert ist und eine Klasse von der anderen abgeleitet wird. Der dominante Name ist der Name der abgeleiteten Klasse; dieser Name wird verwendet, wenn sich anderenfalls eine Doppeldeutigkeit ergeben hätte, wie im folgenden Beispielen veranschaulicht wird:
// deriv_Dominance.cpp
// compile with: /LD
class A {
public:
int a;
};
class B : public virtual A {
public:
int a();
};
class C : public virtual A {};
class D : public B, public C {
public:
D() { a(); } // Not ambiguous. B::a() dominates A::a.
};
Mehrdeutige Konvertierungen
Explizite und implizite Konvertierungen von Zeigern oder Verweisen auf Klassentypen kann zu Mehrdeutigkeiten führen. Die nächste Abbildung (Mehrdeutige Konvertierung der Zeiger auf Basisklassen) zeigt Folgendes:
Die Deklaration eines Objekts vom Typ
D
.Die Auswirkung der Anwendung der Adresse des Operators (&) auf dieses Objekt. Die Adresse des Operators liefert immer die Basisadresse des Objekts.
Der Effekt der expliziten Konvertierung des Zeigers, der unter Verwendung des address-of-Operators für den Basisklassentyp
A
abgerufen wurde. Durch die Koercierung der Adresse des TypsA*
des Typs wird der Compiler nicht immer mit ausreichendEn Informationen darüber bereitgestellt, welches Unterobjekt vom TypA
ausgewählt werden soll. In diesem Fall sind zwei Unterobjekte vorhanden.
Das Diagramm zeigt zunächst eine Vererbungshierarchie: A ist die Basisklasse. B und C erben von A. D erben von B und C. Anschließend wird das Speicherlayout für Objekt D angezeigt. Es gibt drei Unterobjekte in D: B (einschließlich eines Unterobjekts A) und C (einschließlich eines Unterobjekts A). Der Code &d verweist auf das A im Unterobjekt B. Der Code ( * A ) & d verweist auf Subobjekt B und Subobjekt C.
Mehrdeutige Konvertierung von Zeigern in Basisklassen
Die Konvertierung in Typ A*
(Zeiger auf A
) ist mehrdeutig, da es keine Möglichkeit gibt, zu erkennen, welches Unterobjekt vom Typ A
das richtige ist. Sie können die Mehrdeutigkeit vermeiden, indem Sie explizit angeben, welches Unterobjekt Sie verwenden möchten, wie folgt:
(A *)(B *)&d // Use B subobject.
(A *)(C *)&d // Use C subobject.
Mehrdeutigkeiten und virtuelle Basisklassen
Wenn virtuelle Basisklassen verwendet werden, können Funktionen, Objekte, Typen und Enumeratoren über Mehrfachvererbungspfade erreicht werden. Da es nur eine Instanz der Basisklasse gibt, gibt es beim Zugriff auf diese Namen keine Mehrdeutigkeit.
Die folgende Abbildung zeigt, wie Objekte mithilfe von virtueller und nicht virtueller Vererbung zusammengestellt werden.
Das Diagramm zeigt zunächst eine Vererbungshierarchie: A ist die Basisklasse. B und C erben praktisch von A. D und erbt praktisch von B und C. Anschließend wird das Layout von D angezeigt. D enthält Unterobjekte B und C, die Teilobjekt A gemeinsam nutzen. Anschließend wird das Layout so dargestellt, als ob die gleiche Hierarchie mit nichtvirtualer Vererbung abgeleitet wurde. In diesem Fall enthält D die Unterobjekte B und C. Sowohl B als auch C enthalten eine eigene Kopie des Unterobjekts A.
Virtuelle und nichtvirtuale Ableitung
In der Abbildung führt der Zugriff auf beliebige Member der A
-Klasse durch nicht virtuelle Basisklassen zu einer Mehrdeutigkeit. Der Compiler hat keine Informationen darüber, ob das B
zugeordnete Unterobjekt oder das C
zugeordnete Unterobjekt zu verwenden ist. Wenn A
sie jedoch als virtuelle Basisklasse angegeben ist, gibt es keine Frage, auf welches Unterobjekt zugegriffen wird.
Siehe auch
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für