runtime polymorphism c
Eine detaillierte Studie zum Laufzeitpolymorphismus in C ++.
Laufzeitpolymorphismus ist auch als dynamischer Polymorphismus oder späte Bindung bekannt. Beim Laufzeitpolymorphismus wird der Funktionsaufruf zur Laufzeit aufgelöst.
Im Gegensatz dazu leitet der Compiler zur Kompilierungszeit oder zum statischen Polymorphismus das Objekt zur Laufzeit ab und entscheidet dann, welcher Funktionsaufruf an das Objekt gebunden werden soll. In C ++ wird der Laufzeitpolymorphismus mithilfe der Methodenüberschreibung implementiert.
In diesem Tutorial werden wir uns eingehend mit dem Laufzeitpolymorphismus befassen.
Hinzufügen eines Elements zu einem Array Java
=> Überprüfen Sie ALLE C ++ - Tutorials hier.
Was du lernen wirst:
- Funktionsüberschreibung
- Virtuelle Funktion
- Arbeiten von virtuellen Tabellen und _vptr
- Reine virtuelle Funktionen und abstrakte Klasse
- Virtuelle Destruktoren
- Fazit
- Literatur-Empfehlungen
Funktionsüberschreibung
Das Überschreiben von Funktionen ist der Mechanismus, mit dem eine in der Basisklasse definierte Funktion in der abgeleiteten Klasse erneut definiert wird. In diesem Fall wird die Funktion in der abgeleiteten Klasse überschrieben.
Wir sollten uns daran erinnern, dass das Überschreiben von Funktionen nicht innerhalb einer Klasse durchgeführt werden kann. Die Funktion wird nur in der abgeleiteten Klasse überschrieben. Daher sollte eine Vererbung für das Überschreiben von Funktionen vorhanden sein.
Die zweite Sache ist, dass die Funktion einer Basisklasse, die wir überschreiben, dieselbe Signatur oder denselben Prototyp haben sollte, d. H. Sie sollte denselben Namen, denselben Rückgabetyp und dieselbe Argumentliste haben.
Lassen Sie uns ein Beispiel sehen, das das Überschreiben von Methoden demonstriert.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Ausgabe:
Klasse :: Basis
Klasse :: abgeleitet
Im obigen Programm haben wir eine Basisklasse und eine abgeleitete Klasse. In der Basisklasse haben wir eine Funktion show_val, die in der abgeleiteten Klasse überschrieben wird. In der Hauptfunktion erstellen wir jeweils ein Objekt der Klassen Base und Derived und rufen mit jedem Objekt die Funktion show_val auf. Es erzeugt die gewünschte Ausgabe.
Die obige Bindung von Funktionen unter Verwendung von Objekten jeder Klasse ist ein Beispiel für eine statische Bindung.
Lassen Sie uns nun sehen, was passiert, wenn wir den Basisklassenzeiger verwenden und abgeleitete Klassenobjekte als Inhalt zuweisen.
Das Beispielprogramm ist unten dargestellt:
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() //overridden function { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //Early Binding }
Ausgabe:
Klasse :: Basis
Jetzt sehen wir, dass die Ausgabe 'Class :: Base' ist. Unabhängig davon, welches Typobjekt der Basiszeiger enthält, gibt das Programm den Inhalt der Funktion der Klasse aus, deren Basiszeiger der Typ ist. In diesem Fall wird auch eine statische Verknüpfung durchgeführt.
Um die Ausgabe des Basiszeigers, den korrekten Inhalt und die ordnungsgemäße Verknüpfung zu gewährleisten, werden Funktionen dynamisch gebunden. Dies wird mithilfe des Mechanismus für virtuelle Funktionen erreicht, der im nächsten Abschnitt erläutert wird.
Virtuelle Funktion
Damit die überschriebene Funktion dynamisch an den Funktionskörper gebunden werden soll, machen wir die Basisklassenfunktion mit dem Schlüsselwort „virtual“ virtuell. Diese virtuelle Funktion ist eine Funktion, die in der abgeleiteten Klasse überschrieben wird, und der Compiler führt eine späte oder dynamische Bindung für diese Funktion durch.
Lassen Sie uns nun das obige Programm so ändern, dass es das virtuelle Schlüsselwort wie folgt enthält:
#include using namespace std;. class Base { public: virtual void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //late Binding }
Ausgabe:
Klasse :: abgeleitet
In der obigen Klassendefinition von Base haben wir die Funktion show_val als 'virtuell' festgelegt. Da die Basisklassenfunktion virtuell gemacht wird, erfolgt die Bindung zur Laufzeit, wenn wir dem Basisklassenzeiger ein abgeleitetes Klassenobjekt zuweisen und die Funktion show_val aufrufen.
Da der Basisklassenzeiger ein abgeleitetes Klassenobjekt enthält, ist der Funktionskörper show_val in der abgeleiteten Klasse an die Funktion show_val und damit an die Ausgabe gebunden.
In C ++ kann die überschriebene Funktion in der abgeleiteten Klasse auch privat sein. Der Compiler überprüft den Typ des Objekts nur zur Kompilierungszeit und bindet die Funktion zur Laufzeit. Daher macht es keinen Unterschied, selbst wenn die Funktion öffentlich oder privat ist.
Beachten Sie, dass eine Funktion, die in der Basisklasse als virtuell deklariert ist, in allen abgeleiteten Klassen virtuell ist.
Bisher haben wir jedoch noch nicht diskutiert, wie genau virtuelle Funktionen bei der Identifizierung der richtigen zu bindenden Funktion eine Rolle spielen oder mit anderen Worten, wie spät die Bindung tatsächlich erfolgt.
Die virtuelle Funktion wird zur Laufzeit unter Verwendung des Konzepts der genau an den Funktionskörper gebunden virtuelle Tabelle (VTABLE) und ein versteckter Zeiger wird aufgerufen _vptr.
Beide Konzepte sind interne Implementierungen und können nicht direkt vom Programm verwendet werden.
Arbeiten von virtuellen Tabellen und _vptr
Lassen Sie uns zunächst verstehen, was eine virtuelle Tabelle (VTABLE) ist.
Der Compiler richtet zur Kompilierungszeit jeweils eine VTABLE für eine Klasse mit virtuellen Funktionen sowie für die Klassen ein, die von Klassen mit virtuellen Funktionen abgeleitet sind.
Eine VTABLE enthält Einträge, die Funktionszeiger auf die virtuellen Funktionen sind, die von den Objekten der Klasse aufgerufen werden können. Für jede virtuelle Funktion gibt es einen Funktionszeigereintrag.
Bei rein virtuellen Funktionen ist dieser Eintrag NULL. (Dies ist der Grund, warum wir die abstrakte Klasse nicht instanziieren können).
Die nächste Entität, _vptr, die als vtable-Zeiger bezeichnet wird, ist ein versteckter Zeiger, den der Compiler der Basisklasse hinzufügt. Dieses _vptr zeigt auf die vtable der Klasse. Alle von dieser Basisklasse abgeleiteten Klassen erben das _vptr.
Jedes Objekt einer Klasse, das die virtuellen Funktionen enthält, speichert dieses _vptr intern und ist für den Benutzer transparent. Jeder Aufruf einer virtuellen Funktion mit einem Objekt wird dann mit diesem _vptr aufgelöst.
Nehmen wir ein Beispiel, um die Funktionsweise von vtable und _vtr zu demonstrieren.
#include using namespace std; class Base_virtual { public: virtual void function1_virtual() {cout<<'Base :: function1_virtual()
';}; virtual void function2_virtual() {cout<<'Base :: function2_virtual()
';}; virtual ~Base_virtual(){}; }; class Derived1_virtual: public Base_virtual { public: ~Derived1_virtual(){}; virtual void function1_virtual() { coutfunction2_virtual(); delete (b); return (0); }
Ausgabe:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
Java Interview Frage und Antwort für frischer
Im obigen Programm haben wir eine Basisklasse mit zwei virtuellen Funktionen und einem virtuellen Destruktor. Wir haben auch eine Klasse von der Basisklasse abgeleitet und darin; Wir haben nur eine virtuelle Funktion überschrieben. In der Hauptfunktion wird der abgeleitete Klassenzeiger dem Basiszeiger zugewiesen.
Dann rufen wir beide virtuellen Funktionen mit einem Basisklassenzeiger auf. Wir sehen, dass die überschriebene Funktion aufgerufen wird, wenn sie aufgerufen wird, und nicht die Basisfunktion. Während im zweiten Fall, da die Funktion nicht überschrieben wird, die Basisklassenfunktion aufgerufen wird.
Lassen Sie uns nun sehen, wie das obige Programm intern mit vtable und _vptr dargestellt wird.
Da es zwei Klassen mit virtuellen Funktionen gibt, haben wir gemäß der vorherigen Erklärung zwei vtables - eine für jede Klasse. Außerdem ist _vptr für die Basisklasse vorhanden.
Oben ist die bildliche Darstellung des vtable-Layouts für das obige Programm dargestellt. Die vtable für die Basisklasse ist unkompliziert. Bei der abgeleiteten Klasse wird nur function1_virtual überschrieben.
Daher sehen wir, dass in der abgeleiteten Klasse vtable der Funktionszeiger für function1_virtual auf die überschriebene Funktion in der abgeleiteten Klasse zeigt. Andererseits zeigt der Funktionszeiger für function2_virtual auf eine Funktion in der Basisklasse.
Wenn dem Basiszeiger im obigen Programm ein abgeleitetes Klassenobjekt zugewiesen wird, zeigt der Basiszeiger auf _vptr der abgeleiteten Klasse.
Wenn also der Aufruf b-> function1_virtual () ausgeführt wird, wird die Funktion1_virtual aus der abgeleiteten Klasse aufgerufen, und wenn der Funktionsaufruf b-> function2_virtual () ausgeführt wird, zeigt dieser Funktionszeiger auf die Basisklassenfunktion, die Basisklassenfunktion wird genannt.
Reine virtuelle Funktionen und abstrakte Klasse
Details zu virtuellen Funktionen in C ++ haben wir in unserem vorherigen Abschnitt gesehen. In C ++ können wir auch ein „ reine virtuelle Funktion ”Das ist normalerweise gleich Null.
Die reine virtuelle Funktion wird wie unten gezeigt deklariert.
virtual return_type function_name(arg list) = 0;
Die Klasse, die mindestens eine reine virtuelle Funktion hat, die als „ abstrakte Klasse ”. Wir können die abstrakte Klasse niemals instanziieren, d. H. Wir können kein Objekt der abstrakten Klasse erstellen.
Dies liegt daran, dass wir wissen, dass für jede virtuelle Funktion in der VTABLE (virtuelle Tabelle) ein Eintrag vorgenommen wird. Bei einer rein virtuellen Funktion ist dieser Eintrag jedoch ohne Adresse, wodurch er unvollständig wird. Der Compiler erlaubt es daher nicht, ein Objekt für die Klasse mit unvollständigem VTABLE-Eintrag zu erstellen.
Aus diesem Grund können wir eine abstrakte Klasse nicht instanziieren.
Das folgende Beispiel zeigt die reine virtuelle Funktion sowie die abstrakte Klasse.
#include using namespace std; class Base_abstract { public: virtual void print() = 0; // Pure Virtual Function }; class Derived_class:public Base_abstract { public: void print() { cout <<'Overriding pure virtual function in derived class
'; } }; int main() { // Base obj; //Compile Time Error Base_abstract *b; Derived_class d; b = &d; b->print(); }
Ausgabe:
Überschreiben der reinen virtuellen Funktion in der abgeleiteten Klasse
Im obigen Programm haben wir eine als Base_abstract definierte Klasse, die eine reine virtuelle Funktion enthält, die sie zu einer abstrakten Klasse macht. Dann leiten wir eine Klasse 'Derived_class' von Base_abstract ab und überschreiben den darin enthaltenen reinen virtuellen Funktionsdruck.
In der Hauptfunktion wird nicht diese erste Zeile kommentiert. Dies liegt daran, dass der Compiler einen Fehler ausgibt, wenn wir ihn auskommentieren, da wir kein Objekt für eine abstrakte Klasse erstellen können.
Aber ab der zweiten Zeile funktioniert der Code. Wir können erfolgreich einen Basisklassenzeiger erstellen und ihm dann ein abgeleitetes Klassenobjekt zuweisen. Als nächstes rufen wir eine Druckfunktion auf, die den Inhalt der Druckfunktion ausgibt, die in der abgeleiteten Klasse überschrieben wird.
Lassen Sie uns einige Merkmale der abstrakten Klasse kurz auflisten:
- Wir können keine abstrakte Klasse instanziieren.
- Eine abstrakte Klasse enthält mindestens eine reine virtuelle Funktion.
- Obwohl wir keine abstrakte Klasse instanziieren können, können wir immer Zeiger oder Verweise auf diese Klasse erstellen.
- Eine abstrakte Klasse kann einige Implementierungen wie Eigenschaften und Methoden sowie reine virtuelle Funktionen aufweisen.
- Wenn wir eine Klasse von der abstrakten Klasse ableiten, sollte die abgeleitete Klasse alle reinen virtuellen Funktionen in der abstrakten Klasse überschreiben. Wenn dies nicht der Fall ist, ist die abgeleitete Klasse auch eine abstrakte Klasse.
Virtuelle Destruktoren
Destruktoren der Klasse können als virtuell deklariert werden. Immer wenn wir einen Upcast durchführen, d. H. Das abgeleitete Klassenobjekt einem Basisklassenzeiger zuweisen, können die gewöhnlichen Destruktoren inakzeptable Ergebnisse erzeugen.
Zum Beispiel,Betrachten Sie die folgende Ausstrahlung des gewöhnlichen Destruktors.
#include using namespace std; class Base { public: ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Ausgabe:
Unterschied zwischen Unit Test und Integrationstest
Basisklasse :: Destruktor
Im obigen Programm haben wir eine geerbte abgeleitete Klasse von der Basisklasse. In der Hauptsache weisen wir einem Basisklassenzeiger ein Objekt der abgeleiteten Klasse zu.
Idealerweise sollte der Destruktor, der aufgerufen wird, wenn 'delete b' aufgerufen wird, der der abgeleiteten Klasse sein, aber wir können aus der Ausgabe ersehen, dass der Destruktor der Basisklasse als Basisklassenzeiger darauf aufgerufen wird.
Aus diesem Grund wird der abgeleitete Klassendestruktor nicht aufgerufen und das abgeleitete Klassenobjekt bleibt intakt, was zu einem Speicherverlust führt. Die Lösung hierfür besteht darin, den Basisklassenkonstruktor virtuell zu machen, sodass der Objektzeiger auf den korrekten Destruktor zeigt und eine ordnungsgemäße Zerstörung der Objekte durchgeführt wird.
Die Verwendung des virtuellen Destruktors wird im folgenden Beispiel gezeigt.
#include using namespace std; class Base { public: virtual ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Ausgabe:
Abgeleitete Klasse :: Destruktor
Basisklasse :: Destruktor
Dies ist das gleiche Programm wie das vorherige Programm, außer dass wir vor dem Destruktor der Basisklasse ein virtuelles Schlüsselwort hinzugefügt haben. Indem wir den Basisklassen-Destruktor virtuell machen, haben wir die gewünschte Ausgabe erreicht.
Wir können sehen, dass Destruktoren in umgekehrter Reihenfolge der Objekterstellung aufgerufen werden, wenn wir dem Basisklassenzeiger ein abgeleitetes Klassenobjekt zuweisen und dann den Basisklassenzeiger löschen. Dies bedeutet, dass zuerst der abgeleitete Klassendestruktor aufgerufen wird und das Objekt zerstört wird und dann das Basisklassenobjekt zerstört wird.
Hinweis: In C ++ können Konstruktoren niemals virtuell sein, da Konstruktoren an der Erstellung und Initialisierung der Objekte beteiligt sind. Daher müssen alle Konstruktoren vollständig ausgeführt werden.
Fazit
Der Laufzeitpolymorphismus wird mithilfe der Methodenüberschreibung implementiert. Dies funktioniert gut, wenn wir die Methoden mit ihren jeweiligen Objekten aufrufen. Wenn wir jedoch einen Basisklassenzeiger haben und überschriebene Methoden mit dem Basisklassenzeiger aufrufen, der auf die abgeleiteten Klassenobjekte zeigt, treten aufgrund der statischen Verknüpfung unerwartete Ergebnisse auf.
Um dies zu überwinden, verwenden wir das Konzept der virtuellen Funktionen. Mit der internen Darstellung von vtables und _vptr helfen uns virtuelle Funktionen, die gewünschten Funktionen genau aufzurufen. In diesem Tutorial haben wir uns ausführlich mit dem in C ++ verwendeten Laufzeitpolymorphismus befasst.
Damit schließen wir unsere Tutorials zur objektorientierten Programmierung in C ++ ab. Wir hoffen, dass dieses Tutorial hilfreich ist, um objektorientierte Programmierkonzepte in C ++ besser und gründlicher zu verstehen.
=> Besuchen Sie hier, um C ++ von Grund auf neu zu lernen.
Literatur-Empfehlungen
- Polymorphismus in C ++
- Vererbung in C ++
- Friend-Funktionen in C ++
- Klassen und Objekte in C ++
- Verwendung der Selenium Select-Klasse zur Behandlung von Dropdown-Elementen auf einer Webseite - Selenium Tutorial # 13
- Python-Hauptfunktions-Tutorial mit praktischen Beispielen
- Java Virtual Machine: Wie JVM beim Ausführen von Java-Anwendungen hilft
- So richten Sie LoadRunner VuGen-Skriptdateien und Laufzeiteinstellungen ein