Virtuelle Funktionen (C++)

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu Virtuelle Funktionen - Index


Mit Hilfe virtueller Funktionen können abgeleitete Klassen spezifische Versionen einer Basisklassen-Funktion bereitstellen. Eine virtuelle Funktion wird in der Basisklasse mit dem Schlüsselwort virtual deklariert. Dabei wird der Prototyp der Funktion wie üblich deklariert; anschließend stellen Sie das Schlüsselwort virtual vor die Deklaration. Um eine abstrakte Funktion zu deklarieren (was automatisch eine abstrakte Klasse deklariert), stellen Sie vor den Prototyp das Schlüsselwort virtual und setzen die Funktion auf Null.

virtual int funct1(void);       // Eine virtuelle Funktionsdeklaration.
virtual int funct2(void) = 0;   // abstrakte Funktionsdeklaration

Eine Funktion kann nicht gleichzeitig als ein abstrakter Bezeichner und als eine Definition deklariert werden.

Beispiel:

struct C {
    virtual void f() { } = 0; // unzulässig
};

Die einzig zulässige Syntax lautet:

struct TheClass
{
  virtual void funct3(void) = 0;
};
virtual void TheClass::funct3(void)
{
   // Hier steht beliebiger Programmcode.
};

Anmerkung: Abstrakte virtuelle Funktionen werden unter Abstrakte Klassen behandelt.

Wenn Sie virtual-Funktionen deklarieren, beachten Sie bitte folgende Regeln:

  • Es darf sich nur um Elementfunktionen handeln.
  • Sie können als friend einer anderen Klasse deklariert sein.
  • Sie können keine statischen Elemente sein.

Eine virtual-Funktion muss in einer abgeleiteten Klasse nicht neu definiert werden. Sie können eine Definition in der Basisklasse bereitstellen, so dass alle Aufrufe auf die Basisfunktion zugreifen.

Um eine virtual-Funktion in einer beliebigen abgeleiteten Klasse neu zu definieren, müssen Anzahl und Typ der Argumente in der Basisklassen-Deklaration und in der Deklaration in der abgeleiteten Klasse identisch sein. (Der Fall, in dem neu definierte virtual-Funktionen sich nur im Rückgabetyp unterscheiden, wird weiter unten behandelt.) Von einer neu definierten Funktion wird gesagt, dass sie die Basisklassenfunktion überschreibt.

Sie können die Funktionen int Base::Fun(int) und int Derived::Fun(int) selbst dann deklarieren, wenn sie nicht virtual sind. In diesem Fall wird von int Derived::Fun(int) gesagt, dass es andere Versionen von Fun(int), die in einer beliebigen Basisklasse existieren, verdeckt. Wenn außerdem die Klasse Derived andere Versionen von Fun() definiert (d.h. Versionen von Fun() mit unterschiedlichen Funktionsköpfen), werden solche Versionen überladene Versionen von Fun() genannt.

Rückgabetypen von virtuellen Funktionen

Wenn eine virtual-Funktion neu definiert wird, kann nicht einfach nur ihr Rückgabetyp geändert werden. Um sie korrekt neu zu definieren, muss die neue Definition (in einer abgeleiteten Klasse) genau mit dem Rückgabetyp und den formalen Parametern der ursprünglichen Deklaration übereinstimmen. Wenn zwei Funktionen mit demselben Namen unterschiedliche formale Parameter besitzen, sieht sie C++ als verschieden an, der bei virtual-Funktionen ablaufende Mechanismus greift nicht.

Bestimmte virtual-Funktionen in einer Basisklasse können jedoch in einer abgeleiteten Klasse eine überschreibende Version mit einem Rückgabetyp besitzen, der von dem der überschriebenen Funktion abweicht. Dies ist nur möglich, wenn beide der folgenden Bedingungen erfüllt sind:

  • Die überschriebene virtual-Funktion gibt einen Zeiger oder eine Referenz auf die Basisklasse zurück.
  • Die überschreibende Funktion gibt einen Zeiger oder eine Referenz auf die abgeleitete Klasse zurück.

Enthält sowohl die Basisklasse B als auch die Klasse D (als public von B abgeleitet) eine virtual-Funktion vf, und wird vf für ein Objekt d von D aufgerufen, erfolgt der Aufruf D::vf() auch dann, wenn der Zugriff über einen Zeiger oder eine Referenz auf B erfolgt. Ein Beispiel:

struct X {};// Basisklasse.
struct Y : X(); // abgeleitete Klasse
struct B {
   virtual void vf1();
   virtual void vf2();
   virtual void vf3();
   void f();
   virtual X* pf(); // Rückgabetyp ist ein Zeiger auf die Basis. Dies kann
  // überschrieben werden.
};
class D : public B {
public:
   virtual void vf1(); // Virtual-Spezifizierer ist zulässig, aber nicht notwendig.
   void vf2(int); // nicht virtuell, da andere Argumentliste verwendet wird.
 //  Argumentliste. // dadurch wird B::vf2() verdeckt.
// char vf3();// Ungültig: Nur Änderung des Rückgabetyps!
   void f();
   Y*   pf();// Überschreibende Funktion unterscheidet sich
   //  in Rückgabetyp. Gibt einen Zeiger auf
  // die abgeleitete Klasse zurück.
};
void extf()
{
   D d;// D instanziieren
   B* bp = &d; // Standardkonvertierung von D* in B*
  // bp mit Funktionstabelle initialisieren, die
// für Objekt d zur Verfügung steht. Wenn in
  // der d-Tabelle kein Eintrag für eine Funktion
     // steht, wird Funktion in B-Tabelle verwendet.
   bp–>vf1();  // Ruft D::vf1 auf
   bp–>vf2();  // Ruft B::vf2 auf, da D::vf2 andere Argumente hat
   bp–>f();    // Ruft B::f auf (nicht virtuell)
   X* xptr = bp–>pf();   // Ruft D::pf() auf und konvertiert
   // Ergebnis in einen Zeiger auf X.
   D* dptr = &d;
   Y* yptr = dptr–>pf();// Ruft D::pf() auf und initialisiert yptr.
   // Es erfolgt keine weitere Konvertierung.
}

Die überschreibende Funktion vf1 in D ist automatisch virtual. Der Spezifizierer virtual darf in der abgeleiteten Klasse mit einer überschreibenden Funktionsdeklaration verwendet werden. Wenn von D andere Klassen abgeleitet werden, ist das Schlüsselwort virtual erforderlich. Werden von D keine weiteren Klassen abgeleitet, ist der Gebrauch von virtual überflüssig.

Die Interpretation einer virtual-Funktion hängt vom Typ des Objekts ab, für das der Aufruf erfolgt. Bei nicht-virtuellen Funktionsaufrufen ist die Interpretation nur vom Typ des Zeigers oder der Referenz abhängig, der bzw. die das betreffende Objekt bezeichnet.

virtual-Funktionen bezahlen einen Preis für ihre Vielseitigkeit: Jedes Objekt in der abgeleiteten Klasse muss einen Zeiger auf eine Funktionstabelle enthalten, damit zur Laufzeit (späte Bindung) die richtige ausgewählt wird.

Siehe auch