logo

Virtuelle Funktion in C++

Eine virtuelle Funktion (auch als virtuelle Methoden bezeichnet) ist eine Mitgliedsfunktion, die innerhalb einer Basisklasse deklariert und von einer abgeleiteten Klasse neu definiert (überschrieben) wird. Wenn Sie mithilfe eines Zeigers oder einer Referenz auf die Basisklasse auf ein abgeleitetes Klassenobjekt verweisen, können Sie eine virtuelle Funktion für dieses Objekt aufrufen und die Version der Methode der abgeleiteten Klasse ausführen.

  • Virtuelle Funktionen stellen sicher, dass für ein Objekt die richtige Funktion aufgerufen wird, unabhängig von der Art der Referenz (oder des Zeigers), die für den Funktionsaufruf verwendet wird.
  • Sie werden hauptsächlich verwendet, um Laufzeitpolymorphismus zu erreichen.
  • Funktionen werden mit a deklariert virtuell Schlüsselwort in einer Basisklasse.
  • Die Auflösung eines Funktionsaufrufs erfolgt zur Laufzeit.

Regeln für virtuelle Funktionen

Die Regeln für die virtuellen Funktionen in C++ lauten wie folgt:

  1. Virtuelle Funktionen können nicht statisch sein.
  2. Eine virtuelle Funktion kann eine Freundfunktion einer anderen Klasse sein.
  3. Auf virtuelle Funktionen sollte über einen Zeiger oder eine Referenz des Basisklassentyps zugegriffen werden, um Laufzeitpolymorphismus zu erreichen.
  4. Der Prototyp virtueller Funktionen sollte sowohl in der Basisklasse als auch in der abgeleiteten Klasse derselbe sein.
  5. Sie werden immer in der Basisklasse definiert und in einer abgeleiteten Klasse überschrieben. Es ist nicht zwingend erforderlich, dass die abgeleitete Klasse die virtuelle Funktion überschreibt (oder neu definiert). In diesem Fall wird die Basisklassenversion der Funktion verwendet.
  6. Eine Klasse kann einen virtuellen Destruktor haben, aber keinen virtuellen Konstruktor.

Kompilierzeitverhalten (frühe Bindung) vs. Laufzeitverhalten (späte Bindung) virtueller Funktionen

Betrachten Sie das folgende einfache Programm, das das Laufzeitverhalten virtueller Funktionen zeigt.



in.next Java

C++




// C++ program to illustrate> // concept of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >virtual> void> print() { cout <<>'print base class '>; }> >void> show() { cout <<>'show base class '>; }> };> class> derived :>public> base {> public>:> >void> print() { cout <<>'print derived class '>; }> >void> show() { cout <<>'show derived class '>; }> };> int> main()> {> >base* bptr;> >derived d;> >bptr = &d;> >// Virtual function, binded at runtime> >bptr->print();> >// Non-virtual function, binded at compile time> >bptr->show();> >return> 0;> }>

>

>

Ausgabe

print derived class show base class>

Erläuterung: Laufzeitpolymorphismus wird nur durch einen Zeiger (oder eine Referenz) des Basisklassentyps erreicht. Außerdem kann ein Basisklassenzeiger sowohl auf die Objekte der Basisklasse als auch auf die Objekte der abgeleiteten Klasse verweisen. Im obigen Code enthält der Basisklassenzeiger „bptr“ die Adresse des Objekts „d“ der abgeleiteten Klasse.

Was ist Autowired in Java?

Die späte Bindung (Laufzeit) erfolgt entsprechend dem Inhalt des Zeigers (d. h. der Ort, auf den der Zeiger zeigt) und die frühe Bindung (Kompilierungszeit) erfolgt entsprechend dem Zeigertyp, da die print()-Funktion mit dem virtuellen deklariert wird Schlüsselwort, damit es zur Laufzeit gebunden wird (Ausgabe ist Abgeleitete Klasse drucken da der Zeiger auf ein Objekt der abgeleiteten Klasse zeigt) und show() nicht virtuell ist, wird es also während der Kompilierungszeit gebunden (Ausgabe ist Basisklasse anzeigen da der Zeiger vom Basistyp ist).

Notiz: Wenn wir in der Basisklasse eine virtuelle Funktion erstellt haben und diese in der abgeleiteten Klasse überschrieben wird, benötigen wir in der abgeleiteten Klasse kein virtuelles Schlüsselwort. Funktionen werden in der abgeleiteten Klasse automatisch als virtuelle Funktionen betrachtet.

Funktionsweise virtueller Funktionen (Konzept von VTABLE und VPTR)

Wie hier erläutert, führt der Compiler selbst zwei Dinge aus, wenn eine Klasse eine virtuelle Funktion enthält.

  1. Wenn ein Objekt dieser Klasse erstellt wird, dann a virtueller Zeiger (VPTR) wird als Datenelement der Klasse eingefügt, um auf die VTABLE dieser Klasse zu verweisen. Für jedes neu erstellte Objekt wird ein neuer virtueller Zeiger als Datenelement dieser Klasse eingefügt.
  2. Unabhängig davon, ob das Objekt erstellt wird oder nicht, enthält die Klasse ein Mitglied ein statisches Array von Funktionszeigern namens VTABLE . Zellen dieser Tabelle speichern die Adresse jeder in dieser Klasse enthaltenen virtuellen Funktion.

Betrachten Sie das folgende Beispiel:

virtueller Zeiger und virtuelle Tabelle

C++

C-Programmierung einschließen




// C++ program to illustrate> // working of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >void> fun_1() { cout <<>'base-1 '>; }> >virtual> void> fun_2() { cout <<>'base-2 '>; }> >virtual> void> fun_3() { cout <<>'base-3 '>; }> >virtual> void> fun_4() { cout <<>'base-4 '>; }> };> class> derived :>public> base {> public>:> >void> fun_1() { cout <<>'derived-1 '>; }> >void> fun_2() { cout <<>'derived-2 '>; }> >void> fun_4(>int> x) { cout <<>'derived-4 '>; }> };> int> main()> {> >base* p;> >derived obj1;> >p = &obj1;> >// Early binding because fun1() is non-virtual> >// in base> >p->fun_1();> >// Late binding (RTP)> >p->fun_2();> >// Late binding (RTP)> >p->fun_3();> >// Late binding (RTP)> >p->fun_4();> >// Early binding but this function call is> >// illegal (produces error) because pointer> >// is of base type and function is of> >// derived class> >// p->fun_4(5);> >return> 0;> }>

>

>

Ausgabe

base-1 derived-2 base-3 base-4>

Erläuterung: Zunächst erstellen wir einen Zeiger vom Typ Basisklasse und initialisieren ihn mit der Adresse des abgeleiteten Klassenobjekts. Wenn wir ein Objekt der abgeleiteten Klasse erstellen, erstellt der Compiler einen Zeiger als Datenelement der Klasse, der die Adresse von VTABLE der abgeleiteten Klasse enthält.

osi-Referenzmodell in der Vernetzung

Ein ähnliches Konzept von Späte und frühe Bindung wird wie im obigen Beispiel verwendet. Für den Funktionsaufruf fun_1() wird die Basisklassenversion der Funktion aufgerufen, fun_2() wird in der abgeleiteten Klasse überschrieben, sodass die abgeleitete Klassenversion aufgerufen wird, fun_3() wird in der abgeleiteten Klasse nicht überschrieben und ist eine virtuelle Funktion Daher wird die Basisklassenversion aufgerufen. Ebenso wird fun_4() nicht überschrieben, sodass die Basisklassenversion aufgerufen wird.

Notiz: fun_4(int) in der abgeleiteten Klasse unterscheidet sich von der virtuellen Funktion fun_4() in der Basisklasse, da die Prototypen beider Funktionen unterschiedlich sind.

Einschränkungen virtueller Funktionen

    Langsamer: Der Funktionsaufruf dauert aufgrund des virtuellen Mechanismus etwas länger und erschwert die Optimierung für den Compiler, da er zur Kompilierungszeit nicht genau weiß, welche Funktion aufgerufen wird. Schwierig zu debuggen: In einem komplexen System können virtuelle Funktionen es etwas schwieriger machen, herauszufinden, von wo eine Funktion aufgerufen wird.