Share via


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 von CollectionOfBook vor dem Collection-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:

Diagram of a simulated lunch line.

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):

Diagram of a simulated lunch line object.

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).

Diagram of a simulated lunch line object, with virtual base classes depicted.

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:

Diagram of virtual and non virtual components of a class.

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 LunchQueueQueue als virtuelle Basisklasse. Allerdings spezifiziert TakeoutQueueQueue 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.

Diagram of the object layout for virtual and non virtual inheritance.

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 Bbezieht:

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 Bhandelt, 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:

  1. Wenn der Zugriff auf den Namen mehrdeutig ist (wie gerade beschrieben) , wird eine Fehlermeldung generiert.

  2. Wenn überladene Funktionen eindeutig sind, werden sie aufgelöst.

  3. 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 Typs A* des Typs wird der Compiler nicht immer mit ausreichendEn Informationen darüber bereitgestellt, welches Unterobjekt vom Typ A ausgewählt werden soll. In diesem Fall sind zwei Unterobjekte vorhanden.

Diagram showing how the conversion of pointers to base classes can be ambiguous.

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.

Diagram showing virtual derivation and nonvirtual derivation.

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

Vererbung