C++-Programmierung/ Objektorientierte Programmierung/ Virtuelle Methoden


Einleitung

Bearbeiten

In diesem Abschnitt geht es um virtuelle Methoden. Dies sind Methoden, bei denen man erwartet, dass sie in abgeleiteten Klassen redefiniert werden. Der Hauptnutzen von virtuellen Methoden ist die korrekte Verwendung von gleichnamigen Mitgliedsmethoden in einer Vererbungshierarchie. So kann bewirkt werden, dass die Verwendung eines Basisklassenzeigers oder einer Basisklassenreferenz bei Aufruf die Methode am Ende der Hierarchie aufruft, ohne dass diese an der jeweiligen Stelle bekannt sein muss. So wird erreicht, dass das Objekt nicht in einen ungültigen Zustand gerät, wenn eine Instanz einer ggf. mehrfach abgeleiteten Klasse über ihre Basisklassenschnittstelle angesprochen wird.

Was bringen sie?

Bearbeiten

Virtuelle Methoden ermöglichen es dem Übersetzer, die passendste Methode in der Klassenhierarchie zu finden. Wird auf dieses reservierte Wort verzichtet, so wird im Zweifelsfall immer die Methode mit der gleichen Signatur des Urahnen genommen.

Tipp

Virtuelle Methoden gibt es nicht in allen objektorientierten Sprachen. So entspricht sie in Java einer ganz normalen Methode, die nicht final ist.

#include <iostream>

class Tier{
public:
    virtual void iss() { std::cout << "Fresse wie ein Tier" << std::endl; };
//  void iss() { std::cout << "Fresse wie ein Tier" << std::endl; };
};

class Hund : public Tier{
public:
    void iss() { std::cout << "Wuff! Fresse gerade" << std::endl; };
};

class Mensch : public Tier{
public:
    void iss() { std::cout << "Esse gerade" << std::endl; };
};

#include <vector>

int main(){
    std::vector<Tier*> tiere;
    tiere.push_back(new Tier());
    tiere.push_back(new Hund());
    tiere.push_back(new Mensch());

    for (std::vector<Tier*>::const_iterator it = tiere.begin(); it != tiere.end(); it++){
        (*it)->iss();
        delete *it;
    }

    return 0;
}
Ausgabe:
Fresse wie ein Tier
Wuff! Fresse gerade
Esse gerade

Würden wir im obigen Beispiel das virtual entfernen, so hätten wir das Ergebnis

#include <iostream>

class Tier{
public:
    void iss() { std::cout << "Fresse wie ein Tier" << std::endl; };
};

[...]
Ausgabe:
Fresse wie ein Tier
Fresse wie ein Tier
Fresse wie ein Tier

Virtuelle Destruktoren

Bearbeiten

Eine weitere Eigenheit von C++ sind Destruktoren, die für Tätigkeiten wie Speicherfreigabe verwendet werden. Jede Klasse, deren Attribute nicht primitive Typen sind oder die andere Ressourcen verwendet (wie z.B. eine Datenbankverbindung) sollten diese unbedingt in ihren Destruktoren freigeben. Um auf den richtigen Destruktor immer zugreifen zu können, muss der Destruktor des Urahnen mit dem Wort virtual beginnen.

Folgendes Beispiel zeigt die Verwendung und Vererbung von nicht-virtuellen Destruktoren, was zu undefiniertem Verhalten führt.

#include <iostream>

class A{
public:
    A() { }
    ~A() { std::cout << "Zerstöre A" << std::endl; }
 };
 
class B : public A{
public:
    B() { }
    ~B() { std::cout << "Zerstöre B" << std::endl; }
 };
 
int main() {
    A* b1 = new B;
    B* b2 = new B;
 
    delete b1;  // Gemäß C++-Standard undefiniertes Verhalten.
                // Meist wird nur ~A() aufgerufen, da ~A() nicht virtuell.
    delete b2;  // Destruktoren ~B() und ~A() werden aufgerufen
 
    return 0;
}
Ausgabe:
Zerstöre A
Zerstöre B
Zerstöre A