[c++] Warum brauchen wir virtuelle Funktionen in C ++?


Answers

Ohne "virtuell" erhält man "früh verbindlich". Welche Implementierung der Methode verwendet wird, wird zur Kompilierzeit basierend auf dem Typ des Zeigers entschieden, den Sie aufrufen.

Mit "virtuell" erhalten Sie "späte Bindung". Welche Implementierung der Methode verwendet wird, wird zur Laufzeit basierend auf dem Typ des zu zeigenden Objekts entschieden - woraus es ursprünglich konstruiert wurde. Dies ist nicht notwendigerweise der, den Sie aufgrund des Typs des Zeigers meinen, der auf dieses Objekt zeigt.

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

EDIT - siehe diese Frage .

Außerdem - dieses Tutorial behandelt die frühe und späte Bindung in C ++.

Question

Ich lerne C ++ und komme gerade in virtuelle Funktionen.

Was ich (im Buch und online) gelesen habe, sind virtuelle Funktionen Funktionen in der Basisklasse, die Sie in abgeleiteten Klassen überschreiben können.

Aber früher im Buch, als ich etwas über die grundlegende Vererbung lernte, war ich in der Lage, Basisfunktionen in abgeleiteten Klassen ohne Verwendung von virtual zu überschreiben.

Also was fehlt mir hier? Ich weiß, dass es mehr virtuelle Funktionen gibt, und es scheint wichtig zu sein, also möchte ich klarstellen, was genau das ist. Ich kann einfach keine direkte Antwort online finden.




Wenn die Basisklasse Base ist und eine abgeleitete Klasse Der , können Sie einen Base *p Zeiger haben, der tatsächlich auf eine Instanz von Der . Wenn Sie p->foo(); aufrufen p->foo(); Wenn foo nicht virtuell ist, dann wird die Version von Base ausgeführt, wobei ignoriert wird, dass p tatsächlich auf einen Der . Wenn foo virtuell ist, führt p->foo() das "leafmost" -Überschreiben von foo , wobei die tatsächliche Klasse des pointierten Objekts vollständig berücksichtigt wird. Daher ist der Unterschied zwischen virtuell und nicht-virtuell ziemlich entscheidend: erstere erlauben den Laufzeit- polymorphism , das Kernkonzept der OO-Programmierung, während die anderen dies nicht tun.




Sie müssen zwischen Übersteuern und Überladen unterscheiden. Ohne das virtual Schlüsselwort überlasten Sie nur eine Methode einer Basisklasse. Das bedeutet nichts anderes als sich zu verstecken. Nehmen wir an, Sie haben eine Basisklasse Base und eine abgeleitete Klasse Specialized die beide void foo() implementieren. Jetzt haben Sie einen Zeiger auf Base , der auf eine Instanz von Specialized . Wenn Sie foo() aufrufen, können Sie den Unterschied beobachten, den virtual macht: Wenn die Methode virtuell ist, wird die Implementierung von Specialized verwendet, wenn sie fehlt, wird die Version von Base ausgewählt. Es empfiehlt sich, Methoden niemals von einer Basisklasse zu überladen. Eine Methode, die nicht virtuell ist, ist die Methode ihres Autors, um zu sagen, dass ihre Erweiterung in Unterklassen nicht beabsichtigt ist.




Wir brauchen virtuelle Methoden zur Unterstützung von "Laufzeitpolymorphismus". Wenn Sie mit einem Zeiger oder einem Verweis auf die Basisklasse auf ein abgeleitetes Klassenobjekt verweisen, können Sie eine virtuelle Funktion für dieses Objekt aufrufen und die abgeleitete Klassenversion der Funktion ausführen.




Notwendigkeit für virtuelle Funktion erklärt [einfach zu verstehen]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

Ausgabe wird sein:

Hello from Class A.

Aber mit virtueller Funktion:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

Ausgabe wird sein:

Hello from Class B.

Daher können Sie mit der virtuellen Funktion Laufzeitpolymorphie erreichen.




Das Schlüsselwort virtual teilt dem Compiler mit, dass es keine frühe Bindung durchführen soll. Stattdessen sollten automatisch alle Mechanismen installiert werden, die für die späte Bindung erforderlich sind. Um dies zu erreichen, erstellt der typische Compiler1 eine einzelne Tabelle (die VTABLE genannt wird) für jede Klasse, die virtuelle Funktionen enthält. Der Compiler platziert die Adressen der virtuellen Funktionen für diese spezielle Klasse in der VTABLE. In jeder Klasse mit virtuellen Funktionen platziert sie heimlich einen Zeiger, den vpointer (abgekürzt als VPTR), der auf VTABLE für dieses Objekt zeigt. Wenn Sie einen virtuellen Funktionsaufruf über einen Basisklassenzeiger ausführen, fügt der Compiler ruhig Code ein, um den VPTR abzurufen und die Funktionsadresse in VTABLE nachzuschlagen, wodurch die korrekte Funktion aufgerufen wird und eine späte Bindung ausgelöst wird.

Mehr Details in diesem Link http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html




Es hilft, wenn Sie die zugrunde liegenden Mechanismen kennen. C ++ formalisiert einige Codierungstechniken, die von C-Programmierern verwendet werden, "Klassen", die unter Verwendung von "Überlagerungen" ersetzt wurden - Strukturen mit gemeinsamen Kopfabschnitten würden verwendet werden, um Objekte verschiedener Typen, aber mit einigen gemeinsamen Daten oder Operationen zu behandeln. Normalerweise hat die Basisstruktur der Überlagerung (der gemeinsame Teil) einen Zeiger auf eine Funktionstabelle, die auf einen anderen Satz von Routinen für jeden Objekttyp zeigt. C ++ macht dasselbe, verbirgt aber die Mechanismen, dh C ++ ptr->func(...) wo func virtuell ist wie C (*ptr->func_table[func_num])(ptr,...) , wo sich was ändert zwischen abgeleiteten Klassen ist der Inhalt von func_table. [Eine nicht virtuelle Methode ptr-> func () wird nur in mangled_func (ptr, ..) übersetzt.]

Das Ergebnis davon ist, dass Sie nur die Basisklasse verstehen müssen, um die Methoden einer abgeleiteten Klasse aufzurufen, dh wenn eine Routine Klasse A versteht, können Sie ihr einen abgeleiteten Klassen-B-Zeiger übergeben, dann werden die aufgerufenen virtuellen Methoden diese sein von B eher als von A, da du durch die Funktionstabelle B hindurch gehst.




Wenn Sie eine Funktion in der Basisklasse haben, können Sie sie in der abgeleiteten Klasse neu Redefine oder Override .

Methode neu definieren : In der abgeleiteten Klasse wird eine neue Implementierung für die Methode der Basisklasse angegeben. Erleichtert nicht die Dynamic binding .

Überschreiben einer Methode : Redefining einer virtual method der Basisklasse in der abgeleiteten Klasse. Die virtuelle Methode erleichtert die dynamische Bindung .

Also als du gesagt hast:

Aber früher im Buch, als ich etwas über grundlegende Vererbung lernte, war ich in der Lage, Basismethoden in abgeleiteten Klassen zu überschreiben, ohne "virtuell" zu verwenden.

Sie haben es nicht überschrieben, da die Methode in der Basisklasse nicht virtuell war, sondern Sie sie neu definiert haben




Warum brauchen wir virtuelle Funktionen?

Virtuelle Funktionen vermeiden unnötige Typumwandlungsprobleme, und einige von uns können darüber debattieren, warum wir virtuelle Funktionen benötigen, wenn wir den abgeleiteten Klassenzeiger verwenden können, um die Funktion in abgeleiteten Klassen aufzurufen! Die Antwort ist - sie hebt die Idee der Vererbung im großen System auf Entwicklung, wo es sehr wünschenswert ist, ein einzelnes Pointer-Basisklassenobjekt zu haben.

Lassen Sie uns im Folgenden zwei einfache Programme vergleichen, um die Bedeutung von virtuellen Funktionen zu verstehen:

Programm ohne virtuelle Funktionen:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

AUSGABE:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

Programm mit virtueller Funktion:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

AUSGABE:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

Durch die enge Analyse der beiden Ausgaben kann man die Bedeutung von virtuellen Funktionen verstehen.




Virtuelle Methoden werden im Interface Design verwendet. Zum Beispiel in Windows gibt es eine Schnittstelle namens IUnknown wie unten:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

Diese Methoden müssen vom Benutzer der Schnittstelle implementiert werden. Sie sind wesentlich für die Erstellung und Zerstörung bestimmter Objekte, die IUnknown erben müssen. In diesem Fall kennt die Laufzeit die drei Methoden und erwartet, dass sie beim Aufruf implementiert werden. In gewisser Weise wirken sie als ein Vertrag zwischen dem Objekt selbst und dem, was dieses Objekt benutzt.




Related