Virtuelle Funktionen

Das Schlüsselwort virtual dient als Funktionsspezifikator, der Mechanismus für dynamische Auswahl in der Etappe der Ausführung der passenden Funktion-Glied unter Funktionen der Basis- und sekundaeren Klasse, Strukture können keine virtuelle Funktionen haben. Es kann erst für die Änderung Erklärung der Funktionen-Glieder verwendet werden.  

Virtuelle Funktion muss wie eine normale Funktion ausfürenden Körper haben. Beim Aufruf hat sie dieselbe Semantik, wie andere Funktionen. .

Virtuelle Funktion kann in der sekundaeren Klasse substituiert werden.  Die Auswahl der Funktionsdefinition erfolgt dynamisch (in der Ausführungsetappe). Typischer Fall - wenn die Basisklasse virtuelle Funktion hat, und sekundaere Klassen ihre eigenen Versionen dieser Funktion haben.  

Anzeiger auf die Basisklasse kann entweder auf das Objekt der Basisklasse oder auf das Objekt der sekundaeren Klasse hinweisen. Auswahl der aufgerufenen Funktion-Glied wird in der Ausführungsetappe durchgeführt und wird vom Objektyp und nicht vom Anzeigertyp abhängig. Beim Fehlen des Gliedes des sekundären Typs wird vorbestimmt virtuelle Funktion der Basisklasse verwendet.

Destruktors sind immer virtuell unabhängig davon, ob sie mit dem Schluesselwort virtual erklärt werden oder nicht.

Betrachten wir die Verwendung von virtuellen Funktionen am Beispiel des Programms MT5_Tetris.mq5. In einschliessender Datei  MT5_TetrisShape.mqh ist die Basisklasse  CTetrisShape mit der virtuellen Funktion Draw (zeichnen) bestimmt.

//+------------------------------------------------------------------+
class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  };

Weiters wird diese Funktion für jede sekundaere Klasse realisiert entsprechend den Besonderheiten der Klasse-Nachfolger. ZB. hat die erste Figur   CTetrisShape1 ihre eigene Realisierung der Funktion  Draw():

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- Zeichnung der Figur
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- horizontaler Stock
         for(i=0; i<4; i++)
           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
           }
        }
      else
        {
         //--- senkrechter Stock
         for(i=0; i<4; i++)
           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+i*SHAPE_SIZE);
           }
        }
     }
  }

Figur  Quadrat ist von der Klasse CTetrisShape6 beschrieben und hat ihre eigene Realisierung der Methode Draw():

class CTetrisShape6 : public CTetrisShape
  {
public:
   //--- Figurzeichnung
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      for(i=0; i<2; i++)
        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
        }
      for(i=2; i<4; i++)
        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+(i-2)*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+SHAPE_SIZE);
        }
     }
  };

Abhängig davon, Objekt welcher Klasse erzeugt wird, wird virtuelle Funktion einer oder anderer sekundären Klasse aufgerufen.

void CTetrisField::NewShape()
  {
//--- zufallsmässig schaffen wir eine der 7 möglichen Figuren
   int nshape=rand()%7;
   switch(nshape)
     {
      case 0: m_shape=new CTetrisShape1; break;
      case 1: m_shape=new CTetrisShape2; break;
      case 2: m_shape=new CTetrisShape3; break;
      case 3: m_shape=new CTetrisShape4; break;
      case 4: m_shape=new CTetrisShape5; break;
      case 5: m_shape=new CTetrisShape6; break;
      case 6: m_shape=new CTetrisShape7; break;
     }
//--- zeichnen wir
   m_shape.Draw();
//---
  }

Modifier Override #

Der override Modifier bedeutet, dass die zu deklarierende Funktion die Methode der Basisklasse unbedingt überschreiben muss. Dank diesem Modifier kann man Fehler beim Überschreiben vermeiden, wie z.B. zufällige Änderung der Methodensignatur. In der Basisklasse ist zum Beispiel die Methode func definiert, die die Variable vom Typ int als Argument hat:

class CFoo
  {
   void virtual func(int x) const { }
  };

Weiter wird die Methode in der abgeleiteten Klasse überschrieben:

class CBar : public CFoo
  {
   void func(short x) { }
  };

Aber durch einen Fehler ändert sich der Typ des Arguments von int zu short. In diesem Fall wird die Methode nicht mehr überschrieben, sondern überladen. In einigen Situationen kann der Compiler entsprechend dem Algorithmus der überladenen Funktion statt der überschriebenen Methode eine in der Basisklasse definierte Methode auswählen.

Um solche Fehler zu vermeiden, sollte man der Methode den override Modifier hinzufügen.

class CBar : public CFoo
  {
   void func(short x) override { }
  };

Wenn die Signatur beim Überschreiben geändert wird, kann der Compiler keine Methode mit der gleichen Signatur in der Basisklasse finden und gibt einen Kompilierungsfehler aus:

'CBar::func' method is declared with 'override' specifier but does not override any base class method

Modifier final #

Der Modifier final funktioniert umgekehrt: er verbietet, Methoden in abgeleiteten Klassen zu überschreiben. Wenn die Implementierung der Methode abgeschlossen ist, deklarieren Sie diese mit dem Modifier final, damit sie später garantiert nicht verändert wird.

class CFoo
  {
   void virtual func(int x) final { }
  };
 
class CBar : public CFoo
  {
   void func(int) { }
  };
 

Beim Versuch, die Methode mit dem final Modifier zu überschreiben, gibt der Compiler eine Fehlermeldung aus:

'CFoo::func' method declared as 'final' cannot be overridden by 'CBar::func'
see declaration of 'CFoo::func'

Sehen Sie auch

Standardbibliothek