webentwicklung-frage-antwort-db.com.de

Warum virtuelle Funktionen nutzen?

Mögliches Duplikat:
Kann jemand virtuelle C++ - Methoden erklären?

Ich habe eine Frage zu den virtuellen C++ - Funktionen.

Warum und wann nutzen wir virtuelle Funktionen? Kann mir jemand eine Echtzeitimplementierung oder Nutzung von virtuellen Funktionen geben?

39
haris

Sie verwenden virtuelle Funktionen, wenn Sie ein bestimmtes Verhalten (Lesemethode) für Ihre abgeleitete Klasse überschreiben möchten, anstatt das für die Basisklasse implementierte Verhalten, und dies zur Laufzeit über einen Zeiger auf die Basisklasse.

Das klassische Beispiel ist, wenn Sie eine Basisklasse namens Shape und davon abgeleitete konkrete Formen (Klassen) haben. Jede konkrete Klasse überschreibt (implementiert eine virtuelle Methode) mit dem Namen Draw().

Die Klassenhierarchie lautet wie folgt:

Class hierarchy

Das folgende Snippet zeigt die Verwendung des Beispiels. Es wird ein Array von Shape Klassenzeigern erstellt, wobei jeder auf ein bestimmtes abgeleitetes Klassenobjekt verweist. Zur Laufzeit führt das Aufrufen der Draw() -Methode zum Aufrufen der Methode, die von dieser abgeleiteten Klasse überschrieben wird, und die bestimmte Shape wird gezeichnet (oder gerendert).

Shape *basep[] = { &line_obj, &tri_obj,
                   &rect_obj, &cir_obj};
for (i = 0; i < NO_PICTURES; i++)
    basep[i] -> Draw ();

Das obige Programm verwendet nur den Zeiger auf die Basisklasse, um Adressen der abgeleiteten Klassenobjekte zu speichern. Dies führt zu einer losen Kopplung, da sich das Programm nicht drastisch ändern muss, wenn jederzeit eine neue konkrete abgeleitete Klasse von shape hinzugefügt wird. Der Grund dafür ist, dass es nur minimale Codesegmente gibt, die tatsächlich vom konkreten Shape -Typ abhängen.

Das Obige ist ein gutes Beispiel für das Open Closed Principle des berühmten SOLID Design-Prinzips.

53
Alok Save

Sie verwenden virtuelle Funktionen, wenn Sie verschiedene Objekte auf dieselbe Weise behandeln müssen. Es heißt Polymorphismus. Stellen wir uns vor, Sie haben eine Basisklasse - so etwas wie eine klassische Form:

    class Shape
    {
        public:
           virtual void draw() = 0;
           virtual ~Shape() {}
    };

    class Rectange: public Shape
    {
        public:
            void draw() { // draw rectangle here } 
    };


    class Circle: public Shape
    {
        public:
           void draw() { // draw circle here }
    };

Jetzt können Sie Vektoren verschiedener Formen haben:

    vector<Shape*> shapes;
    shapes.Push_back(new Rectangle());
    shapes.Push_back(new Circle());

Und Sie können alle Formen wie folgt zeichnen:

    for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
    {
          (*i)->draw();
    }

Auf diese Weise zeichnen Sie verschiedene Formen mit einer virtuellen Methode - draw (). Die richtige Version der Methode wird anhand der Laufzeitinformationen zum Typ des Objekts hinter dem Zeiger ausgewählt.

Hinweis Wenn Sie virtuelle Funktionen verwenden, können Sie diese als rein virtuell deklarieren (wie in der Klasse Shape setzen Sie nach der Methode proto einfach "= 0"). In diesem Fall können Sie keine Instanz eines Objekts mit einer reinen virtuellen Funktion erstellen und es wird als abstrakte Klasse bezeichnet.

Beachten Sie auch "virtual" vor dem Destruktor. Wenn Sie die Arbeit mit Objekten über Zeiger auf ihre Basisklassen planen, sollten Sie den Destruktor als virtuell deklarieren. Wenn Sie also "delete" für den Basisklassenzeiger aufrufen, werden alle Destruktorketten aufgerufen, und es treten keine Speicherverluste auf.

23
Volodymyr Rudyi

Denken Sie an Tierklasse, und davon abgeleitet sind Katze, Hund und Kuh. Tierklasse hat eine

virtual void SaySomething()
{
    cout << "Something";
}

funktion.

Animal *a;
a = new Dog();
a->SaySomething();

Anstatt "Something" zu drucken, sollte der Hund "Bark" sagen, die Katze sollte "Meow" sagen. In diesem Beispiel sehen Sie, dass a ein Hund ist, aber manchmal haben Sie einen Tierzeiger und wissen nicht, um welches Tier es sich handelt. Sie wollen nicht wissen, um welches Tier es sich handelt, sondern nur, dass das Tier etwas sagt. Sie rufen also einfach die virtuelle Funktion auf und Katzen sagen "meow" und Hunde sagen "bark".

Natürlich sollte die SaySomething-Funktion rein virtuell sein, um mögliche Fehler zu vermeiden.

20
holgac

Sie würden eine virtuelle Funktion verwenden, um "Polymorphismus" zu implementieren, insbesondere wenn Sie ein Objekt haben, nicht wissen, was der eigentliche zugrunde liegende Typ ist, aber wissen, welche Operation Sie darauf ausführen möchten und wie dies implementiert wird (wie es funktioniert) unterscheidet sich je nachdem, welchen Typ Sie tatsächlich haben.

Im wesentlichen das sogenannte "Liskov-Substitutionsprinzip", benannt nach Barbara Liskov, die um 1983 darüber sprach.

Wenn Sie dynamische Laufzeitentscheidungen verwenden müssen, bei denen zu dem Zeitpunkt, an dem der Code aufgerufen wird, der die Funktion aufruft, Sie weder jetzt noch in Zukunft wissen, welche Typen durch die Funktion geleitet werden können, ist dies ein gutes Modell.

Dies ist jedoch nicht der einzige Weg. Es gibt alle Arten von "Rückrufen", die einen "Blob" von Daten aufnehmen können, und Sie haben möglicherweise Rückruftabellen, die von einem Headerblock in den eingehenden Daten abhängen, z. ein Nachrichtenprozessor. Dafür ist es nicht erforderlich, eine virtuelle Funktion zu verwenden. In der Tat würden Sie wahrscheinlich eine Art verwenden, wie eine v-Tabelle nur mit einem Eintrag implementiert wird (z. B. einer Klasse mit nur einer virtuellen Funktion).

1
CashCow