Frage Warum brauchen wir virtuelle Funktionen in C ++?


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 grundlegende Vererbung lernte, war ich in der Lage, Basisfunktionen in abgeleiteten Klassen zu überschreiben, ohne sie zu benutzen virtual.

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.


965
2018-03-06 07:10


Ursprung


Antworten:


Hier habe ich nicht nur was verstanden virtual Funktionen sind, aber warum sie benötigt werden:

Nehmen wir an, Sie haben diese zwei Klassen:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

In Ihrer Hauptfunktion:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

So weit, so gut, oder? Tiere essen Generika, Katzen essen Ratten, alle ohne virtual.

Lass es uns jetzt ein bisschen ändern eat() wird über eine Zwischenfunktion aufgerufen (eine triviale Funktion nur für dieses Beispiel):

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

Jetzt ist unsere Hauptfunktion:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

Oh oh ... wir haben eine Katze reingelassen func(), aber es wird keine Ratten essen. Sollten Sie überladen func() also dauert es a Cat*? Wenn Sie mehr Tiere von Animal herleiten müssen, würden sie alle ihre eigenen brauchen func().

Die Lösung ist zu machen eat() von dem Animal klasse eine virtuelle Funktion:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

Main:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

Erledigt.


2286
2018-03-06 13:54



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"

BEARBEITEN - sehen diese Frage.

Ebenfalls - dieses Tutorial umfasst frühe und späte Bindung in C ++.


533
2018-03-06 07:56



Sie benötigen mindestens 1 Vererbung und einen Downcast, um dies zu demonstrieren. Hier ist ein sehr einfaches Beispiel:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    cout << d->Says();   // always Woof
    cout << a->Says();   // Woof or ?, depends on virtual
}

72
2018-03-06 07:26



Sie benötigen virtuelle Methoden für sicheres Downcasting, Einfachheit und Prägnanz.

Das ist es, was virtuelle Methoden tun: Sie reduzieren sicher, mit scheinbar einfachem und präzisem Code, und vermeiden die unsicheren manuellen Umwandlungen in dem komplexeren und ausführlicheren Code, den Sie sonst hätten.


Nicht-virtuelle Methode ⇒ statische Bindung

Der folgende Code ist absichtlich "falsch". Es erklärt nicht das value Methode als virtualund erzeugt daher ein unbeabsichtigtes "falsches" Ergebnis, nämlich 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

In der Zeile kommentiert als "schlecht" die Expression::value Methode heißt, weil die statisch bekannter Typ (der zum Zeitpunkt der Kompilierung bekannte Typ) ist Expression, und das value Methode ist nicht virtuell.


Virtuelle Methode ⇒ dynamische Bindung.

Erklären value wie virtual in der statisch bekannten Art Expression stellt sicher, dass bei jedem Aufruf überprüft wird, um welchen tatsächlichen Objekttyp es sich handelt, und rufen Sie die entsprechende Implementierung von valuedafür dynamischer Typ:

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Hier ist die Ausgabe 6.86 wie es sein sollte, da die virtuelle Methode ist virtuell aufgerufen. Dies wird auch genannt Dynamische Bindung der Anrufe. Eine kleine Überprüfung wird durchgeführt, wobei der tatsächliche dynamische Objekttyp und die relevante Methodenimplementierung für diesen dynamischen Typ ermittelt werden.

Die relevante Implementierung ist diejenige in der spezifischsten (am weitesten abgeleiteten) Klasse.

Beachten Sie, dass Methodenimplementierungen in abgeleiteten Klassen hier nicht markiert sind virtual, sind aber markiert override. Sie könnten markiert sein virtual aber sie sind automatisch virtuell. Das override Schlüsselwort stellt sicher, dass, wenn es gibt nicht so eine virtuelle Methode in einer Basisklasse, dann erhalten Sie einen Fehler (was wünschenswert ist).


Die Hässlichkeit, dies ohne virtuelle Methoden zu tun

Ohne virtual man müsste einiges umsetzen Mach es selbst Version der dynamischen Bindung. Dies beinhaltet in der Regel unsicheres manuelles Downcasting, Komplexität und Ausführlichkeit.

Für den Fall einer einzelnen Funktion, wie hier, ist es ausreichend, einen Funktionszeiger im Objekt zu speichern und über diesen Funktionszeiger aufzurufen, aber trotzdem beinhaltet es einige unsichere Downcasts, Komplexität und Ausführlichkeit, nämlich:

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Eine positive Möglichkeit, dies zu betrachten, ist, dass, wenn Sie unsicheres Downcasting, Komplexität und Ausführlichkeit wie oben erfahren, dann oft eine virtuelle Methode oder Methoden wirklich helfen können.


28
2017-11-24 07:24



Wenn die Basisklasse ist Baseund eine abgeleitete Klasse ist DerDu kannst eine haben Base *p Zeiger, der tatsächlich auf eine Instanz von zeigt Der. Wenn du anrufst p->foo();, ob foo ist nicht dann virtuell BaseDie Version von ihm ausgeführt, ignoriert die Tatsache, dass p weist eigentlich auf a hin Der. Wenn foo ist virtuell, p->foo() führt den "letzten" Override von aus foounter vollständiger Berücksichtigung der tatsächlichen Klasse des spitzen Gegenstandes. Daher ist der Unterschied zwischen virtuell und nicht-virtuell ziemlich entscheidend: erstere erlauben Laufzeit Polymorphismus, das Kernkonzept der OO-Programmierung, während die letzteren nicht.


27
2018-03-06 07:27



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.


24
2017-12-12 11:56



Virtuelle Funktionen werden zur Unterstützung verwendet Laufzeitpolymorphismus.

Das ist, virtuell Schlüsselwort sagt dem Compiler die Entscheidung (der Funktionsbindung) nicht zur Kompilierzeit zu treffen, sondern sie zur Laufzeit zu verschieben ".

  • Sie können eine Funktion virtuell machen, indem Sie dem Schlüsselwort vorangehen virtual in seiner Basisklasse-Deklaration. Beispielsweise,

     class Base
     {
        virtual void func();
     }
    
  • Wenn ein Basisklasse Hat eine virtuelle Memberfunktion, kann jede Klasse, die von der Basisklasse erbt, dies tun neu definieren die Funktion mit genau der gleiche Prototyp d.h. nur Funktionalität kann neu definiert werden, nicht die Schnittstelle der Funktion.

     class Derive : public Base
     {
        void func();
     }
    
  • Ein Basisklassenzeiger kann verwendet werden, um sowohl auf das Basisklassenobjekt als auch auf ein abgeleitetes Klassenobjekt zu zeigen.

  • Wenn die virtuelle Funktion unter Verwendung eines Basisklassenzeigers aufgerufen wird, entscheidet der Compiler zur Laufzeit, welche Version der Funktion - d. H. Die Basisklassenversion oder die überschriebene abgeleitete Klassenversion - aufgerufen werden soll. Das nennt man Laufzeitpolymorphismus.

23
2017-10-12 09:41



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 umsetzen void foo(). Jetzt haben Sie einen Zeiger auf Base auf eine Instanz von zeigen Specialized. Wenn du anrufst foo() darauf können Sie den Unterschied beobachten, dass virtual macht: Wenn die Methode virtuell ist, wird die Implementierung von Specialized Wird, falls es fehlt, die Version von verwendet Base wird gewä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.


19
2018-03-06 07:27



Warum brauchen wir virtuelle Methoden in C ++?

Schnelle Antwort:

  1. Es liefert uns einen der benötigten "Zutaten"1 zum Objekt orientierte Programmierung.

In Bjarne Stroustrup C ++ Programmierung: Prinzipien und Praxis, (14.3):

Die virtuelle Funktion bietet die Möglichkeit, eine Funktion in einer Basisklasse zu definieren und eine Funktion mit demselben Namen und Typ in einer abgeleiteten Klasse zu verwenden, die aufgerufen wird, wenn ein Benutzer die Basisklassenfunktion aufruft. Das wird oft genannt Laufzeit-Polymorphismus, dynamischer Versand, oder Laufzeitversand weil die aufgerufene Funktion zur Laufzeit basierend auf dem Typ des verwendeten Objekts bestimmt wird.

  1. Es ist die schnellste und effizienteste Implementierung, wenn Sie eine benötigen virtueller Funktionsaufruf  2.

Um einen virtuellen Anruf zu bearbeiten, benötigt man ein oder mehrere Datenstücke, die mit dem Internet verbunden sind abgeleitetes Objekt  3. Die übliche Vorgehensweise besteht darin, die Adresse der Funktionstabelle hinzuzufügen. Diese Tabelle wird normalerweise als bezeichnet virtuelle Tabelle oder virtuelle Funktionstabelle und seine Adresse wird oft als virtueller Zeiger. Jede virtuelle Funktion erhält einen Slot in der virtuellen Tabelle. Abhängig vom Objekttyp (abgeleitet) des Aufrufers ruft die virtuelle Funktion ihrerseits die entsprechende Überschreibung auf.


1. Die Verwendung von Vererbung, Laufzeitpolymorphismus und Kapselung ist die gebräuchlichste Definition von Objekt orientierte Programmierung.

2. Sie können die Funktionalität nicht so programmieren, dass sie schneller ist oder weniger Speicher belegt, wenn Sie andere Sprachfunktionen verwenden, um zur Laufzeit zwischen den Alternativen zu wählen. Bjarne Stroustrup C ++ Programmierung: Prinzipien und Praxis (14.3.1).

3. Etwas zu sagen, welche Funktion tatsächlich aufgerufen wird, wenn wir die Basisklasse aufrufen, die die virtuelle Funktion enthält.


14
2017-09-27 09:37



Ich möchte eine andere Verwendung der virtuellen Funktion hinzufügen, obwohl es das gleiche Konzept wie die oben genannten Antworten verwendet, aber ich denke, es ist erwähnenswert.

VIRTUELLER ZERSTÖRER

Betrachten Sie dieses Programm unten, ohne den Destruktor der Basisklasse als virtuell zu deklarieren; Speicher für Cat kann nicht aufgeräumt werden.

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Ausgabe:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Ausgabe:

Deleting an Animal name Cat
Deleting an Animal

13
2018-03-02 09:44



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

Eine Methode neu definieren


12
2018-02-06 08:29