virtuelle Methoden

Wenn wir bei Vererbung behaupten Derived ist ein Base, dann müsste man es doch auch als B behandeln können, sprich einer Funktion die ein Base erwartet ein Derived übergeben.

Es geht! Aber nur bei 'Call by Reference' und nicht bei 'Call by Value', dh, by Value geht es auch, aber dann wird unser Derived tatsächlich zu einem Base, da eine Kopie angestellt wird und beim kopieren nunmal ein Base erstellt wird. Das nennt sich 'slicing'. Aber mit Referenzen oder Zeigern gibt's da keine Probleme.

Doch was bringt es mir das zu tun? Ein Base hat ja nicht die Methoden von Derived und somit kann die Funktion nichts anderes machen als das Derived wie ein Base zu behandeln... also hätten ich ja gleich ein Base übergeben können...

Aber es gibt virtuelle Methoden. Das bedeutet, dass eine abgeleitete Klasse Methoden der Basisklasse 'überschreiben' kann. Wenn wir nun unser Derived an die Funktion übergeben, wird nicht die virtuelle Methode von Base aufgerufen, sondern die neue von Derived. Das verhalten ändert sich somit. Diese Verhaltensänderung nennt sich Polymorphie.

    #include<iostream>
    using namespace std;

    class Vogel
    {
    public:
      virtual void flieg();
    };

    class dickerVogel : public Vogel
    {
    public:
      void flieg(); //hier muss kein virtual stehen, denn flieg()
                    //wurde schon in Vogel als virtual deklariert
                    //und das virtual kann sich nicht auflösen
    };

    void FliegVogel(Vogel&);

    int main()
    {
      Vogel v;
      dickerVogel dv;

      cout<<"Vogel fliegt:\n";
      FliegVogel(v);

      cout<<"\ndicker Vogel fliegt:\n";
      FliegVogel(dv);
    }

    void FliegVogel(Vogel& v)
    {
      v.flieg();
    }

    void Vogel::flieg()
    {
      cout<<"ich fliiiiiiiiiiiiiege....\n";
    }

    void dickerVogel::flieg()
    {
      //wir rufen mal das urspruengliche flieg() auf
      //das muss man aber nicht tun
      Vogel::flieg();
      cout<<"aaaahhhh pluumps!!!\n";
    }
    

Jedesmal wenn wir v.flieg(); aufrufen, muss der Compiler feststellen, welches flieg() wir aufrufen wollen. Dies kann er dank der vtable (virtuelle Methoden Tabelle). Jede Klasse die virtuelle Methoden besitzt, hat eine vtable. Das bedeutet, dass bei jedem Aufruf einer virtuellen Methode erst ein dispatching stattfinden muss. dies macht virtuelle Methoden etwas langsamer als normale Methoden. Das ist aber kein Grund auf virtuelle Methoden zu verzichten!

Betrachten wir einmal folgendes Beispiel:

    #include<iostream>
    using namespace std;

    class Form
    {
    };

    class Rechteck : public Form
    {
    };

    class Kreis : public Form
    {
    };

    int main()
    {
      Form* array[10];

      for(int i=0;i<10;++i)
      {
        if((i%2)==0) //i%2 liefert fuer alle geraden zahlen 0
        {
          array[i]=new Rechteck;
        }
        else
        {
          array[i]=new Kreis;
        }
      }

      for(int i=0;i<10;++i)
      {
        delete array[i];
      }
    }
    

Das Problem ist erkennbar. In der unteren Schleife wird immer nur den Destruktor von Form aufgerufen, denn der Standard Destruktor ist NICHT virtual. Somit müssen wir immer wenn wir eine Klasse haben von der wir wissen, dass es Klassen gibt/geben wird, die von ihr erben, den Destruktor virtual machen!

    class Form
    {
    public:
      virtual ~Form();
    };
    

top