
Jake Wilson
Ich lerne C++ und beschäftige mich gerade mit virtuellen Funktionen.
Nach dem, was ich gelesen habe (im Buch und online), sind virtuelle Funktionen Funktionen in der Basisklasse, die Sie in abgeleiteten Klassen überschreiben können.
Aber früher in diesem Buch, als ich etwas über grundlegende Vererbung lernte, konnte ich Basisfunktionen in abgeleiteten Klassen ohne Verwendung überschreiben virtual
.
Also was übersehe ich hier? Ich weiß, dass virtuelle Funktionen mehr sind, und es scheint wichtig zu sein, also möchte ich klarstellen, was es genau ist. Ich finde im Internet einfach keine eindeutige Antwort.

M. Perry
Hier ist, wie ich verstanden habe, nicht nur was virtual
Funktionen sind, aber warum sie benötigt werden:
Angenommen, Sie haben diese beiden 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 fressen generisches Futter, Katzen fressen Ratten, alles ohne virtual
.
Ändern wir es jetzt ein wenig damit eat()
wird über eine Zwischenfunktion aufgerufen (nur für dieses Beispiel eine triviale Funktion):
// 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."
Uh oh … wir sind an einer Katze vorbeigegangen func()
, aber es frisst keine Ratten. Sollten Sie überladen func()
also dauert es a Cat*
? Wenn Sie weitere Tiere von Animal ableiten müssten, bräuchten sie alle ihre eigenen 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."; }
};
Hauptsächlich:
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating a rat."
Getan.
Ohne „virtuell“ erhalten Sie „Early Binding“. Welche Implementierung der Methode verwendet wird, wird zur Kompilierzeit basierend auf dem Typ des Zeigers entschieden, den Sie aufrufen.
Mit “virtuell” erhalten Sie “spätes Binden”. Welche Implementierung der Methode verwendet wird, wird zur Laufzeit entschieden, basierend auf dem Typ des Objekts, auf das gezeigt wird – als was es ursprünglich konstruiert wurde. Dies ist nicht unbedingt das, was Sie denken würden, basierend auf dem Typ des Zeigers, 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* basePtr = new Derived ();
// Note - constructed as Derived, but pointer stored as Base*
basePtr->Method1 (); // Prints "Base::Method1"
basePtr->Method2 (); // Prints "Derived::Method2"
BEARBEITEN – Siehe diese Frage.
Ebenfalls – dieses Tutorial behandelt die frühe und späte Bindung in C++.

Henk Holtermann
Sie benötigen mindestens 1 Vererbungsebene und einen Upcast, um dies zu demonstrieren. Hier ein ganz 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
std::cout << d->Says(); // always Woof
std::cout << a->Says(); // Woof or ?, depends on virtual
}
Virtuelle Funktionen werden zur Unterstützung verwendet Laufzeitpolymorphismus.
Das ist, virtuell Schlüsselwort teilt dem Compiler mit die Entscheidung (der Funktionsbindung) nicht zur Kompilierzeit zu treffen, sondern zur Laufzeit aufzuschieben”.
-
Sie können eine Funktion virtuell machen, indem Sie das Schlüsselwort voranstellen virtual
in seiner Basisklassendeklaration. Zum Beispiel,
class Base
{
virtual void func();
}
-
Wenn ein Basisklasse eine virtuelle Mitgliedsfunktion hat, kann jede Klasse, die von der Basisklasse erben kann neu definieren die Funktion mit genau der gleiche Prototyp dh nur die 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 ein 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 – dh die Basisklassenversion oder die überschriebene abgeleitete Klassenversion – aufgerufen werden soll. Das nennt man Laufzeitpolymorphismus.

Prost und hth. – Alf
Sie benötigen virtuelle Methoden für sicheren Niedergang, Einfachheitund Prägnanz.
Das ist es, was virtuelle Methoden tun: Sie setzen sicher mit scheinbar einfachem und prägnantem Code um und vermeiden die unsicheren manuellen Umwandlungen in den komplexeren und ausführlicheren Code, die Sie sonst hätten.
Nicht virtuelle Methode ⇒ statische Bindung ======================================
Der folgende Code ist absichtlich „falsch“. Es erklärt nicht die value
Methode als virtual
und 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 als “schlecht” kommentierten Zeile die Expression::value
Methode aufgerufen, weil die statisch bekannter Typ (der zur Kompilierzeit bekannte Typ) ist Expression
und das value
Methode ist nicht virtuell.
Virtuelle Methode ⇒ dynamische Bindung. =====================================
Deklarieren value
als virtual
in der statisch bekannten Art Expression
stellt sicher, dass jeder Aufruf überprüft, um welchen tatsächlichen Objekttyp es sich handelt, und die entsprechende Implementierung von aufruft value
dafü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 angerufen. Dies wird auch genannt Dynamische Bindung der Anrufe. Es wird eine kleine Überprüfung durchgeführt, um den tatsächlichen dynamischen Objekttyp zu finden, und die relevante Methodenimplementierung für diesen dynamischen Typ wird aufgerufen.
Die relevante Implementierung ist diejenige in der spezifischsten (am meisten abgeleiteten) Klasse.
Beachten Sie, dass Methodenimplementierungen in abgeleiteten Klassen hier nicht gekennzeichnet sind virtual
sondern sind stattdessen gekennzeichnet override
. Sie könnten markiert werden virtual
aber sie sind automatisch virtuell. Die override
Schlüsselwort stellt sicher, dass, wenn es gibt nicht eine solche 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 einige implementieren Mach es selbst Version der dynamischen Bindung. Dies ist im Allgemeinen mit unsicherem manuellem Downcasting, Komplexität und Ausführlichkeit verbunden.
Für den Fall einer einzelnen Funktion, wie hier, reicht es aus, einen Funktionszeiger im Objekt zu speichern und über diesen Funktionszeiger aufzurufen, aber selbst das bringt einige unsichere Downcasts, Komplexität und Ausführlichkeit mit sich, 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 Sichtweise ist, dass wenn Sie wie oben auf unsicheres Downcasting, Komplexität und Ausführlichkeit stoßen, dann oft eine virtuelle Methode oder Methoden wirklich helfen können.

PW
Wenn die Basisklasse ist Base
und eine abgeleitete Klasse ist Der
Sie können eine haben Base *p
Zeiger, der tatsächlich auf eine Instanz von zeigt Der
. Wenn du anrufst p->foo();
wenn foo
ist nicht also virtuell Base
‘s Version davon wird ausgeführt, wobei die Tatsache ignoriert wird, dass p
weist eigentlich auf a hin Der
. Wenn foo ist virtuell, p->foo()
führt die “leafmost” Überschreibung von aus foo
, wobei die tatsächliche Klasse des Elements, auf das gezeigt wird, vollständig berücksichtigt wird. Der Unterschied zwischen virtuell und nicht virtuell ist also eigentlich ziemlich entscheidend: Ersteres ermöglicht Laufzeit Polymorphismusdas Kernkonzept der OO-Programmierung, während letzteres dies nicht tut.

Neuron
Ich möchte eine weitere Verwendung der virtuellen Funktion hinzufügen, obwohl sie dasselbe 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 darf nicht bereinigt 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
10019000cookie-checkWarum brauchen wir virtuelle Funktionen in C++?yes
Dies ist vielleicht der größte Vorteil virtueller Funktionen – die Fähigkeit, Ihren Code so zu strukturieren, dass neu abgeleitete Klassen automatisch ohne Modifikation mit dem alten Code funktionieren!
– Benutzer3530616
29. April 2017 um 13:08 Uhr
tbh, virtuelle Funktionen sind ein Hauptmerkmal von OOP zum Löschen von Typen. Ich denke, es sind nicht-virtuelle Methoden, die Object Pascal und C++ so besonders machen, da sie unnötig große vtable optimieren und POD-kompatible Klassen ermöglichen. Viele OOP-Sprachen erwarten das jeden Methode kann überschrieben werden.
– Swift – Friday Pie
7. April 2018 um 9:28 Uhr
Das ist eine gute Frage. Tatsächlich wird dieses virtuelle Ding in C++ in anderen Sprachen wie Java oder PHP abstrahiert. In C++ erhalten Sie nur für einige seltene Fälle etwas mehr Kontrolle (Beachten Sie die Mehrfachvererbung oder den Sonderfall der DDOD). Aber warum wird diese Frage auf stackoverflow.com gepostet?
– Edgar Alloro
21. Mai 2018 um 21:45 Uhr
Ich denke, wenn Sie sich die frühe Bindung, die späte Bindung und VTABLE ansehen, wäre dies vernünftiger und sinnvoller. Es gibt also eine gute Erklärung ( learncpp.com/cpp-tutorial/125-the-virtual-table ) Hier.
– ceyun
17. Juli 2019 um 7:27 Uhr
Eine bessere Anleitung habe ich noch nicht gefunden: nrecursions.blogspot.com/2015/06/…
– Johannes David
21. Januar 2021 um 12:30 Uhr