Fonctions virtuelles

Le mot-clé virtual sert de spécificateur pour la fonction, et assure le mécanisme de choix dynamique lors de l'exécution de la fonction-membre qui convient (parmi les fonctions des classes de base et dérivée), les structures ne peuvent pas avoir de fonctions virtuelles. Il peut être utilisé pour changer les déclarations des membres de la fonction seulement.

La fonction virtuelle, comme une fonction ordinaire, doit avoir un corps exécutable. A l'appel, sa sémantique est exactement la même, comme dans les autres fonctions.

La fonction virtuelle peut être remplacée dans la classe dérivée. Le choix de quelle définition de fonction appeler pour une fonction virtuelle, est fait dynamiquement (à l'étape de l'exécution). Un cas typique est quand une classe de base contient une fonction virtuelle et que les classes dérivées ont leurs propres versions de cette fonction.

Le pointeur sur la classe de base peut indiquer l'objet de classe, ou l'objet d'une classe dérivée. Le choix de la fonction-membre appelée sera effectué à l'étape de l'exécution et dépendra du type de l'objet, et non du type du pointeur. S'il n'y a aucun membre d'un type dérivé, une fonction virtuelle de la classe de base est utilisée par défaut.  

Les destructeurs sont toujours virtuels, indépendamment du fait qu'ils soient déclarés avec le mot-clé virtual ou non.

Considérons l'utilisation des fonctions virtuelles avec l'exemple du programme MT5_Tetris.mq5. La classe de base CTetrisShape avec une fonction virtuelle Draw (dessiner) est définie dans le fichier inclus MT5_TetrisShape.mqh.

//+------------------------------------------------------------------+
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[]);
  };

Ensuite, pour chaque classe dérivée, cette fonction est implémentée conformément aux particularités de la classe-descendante. Par exemple, la première figure CTetrisShape1 a son implémentation de fonction Draw() :

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- dessin de la figure
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- ligne horizontale
         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
        {
         //--- ligne verticale
         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);
           }
        }
     }
  }

La figure "carré" est décrite par la classe CTetrisShape6 et elle a sa propre implémentation de méthode Draw() :

class CTetrisShape6 : public CTetrisShape
  {
public:
   //--- dessin de la figure
   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);
        }
     }
  };

En fonction de l'objet, une fonction virtuelle de n'importe quelle classe dérivée est appelée.

void CTetrisField::NewShape()
  {
//--- créons une de 7 figures possibles par hasard
 
   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;
     }
//--- dessin
   m_shape.Draw();
//---
  }

Modifier 'override' #

Le modificateur 'override' signifie que la fonction déclarée doit dériver la méthode de sa classe parente. L'utilisation de ce mot-clé vous permet d'éviter des erreurs de dérivation, par exemple il vous permet d'éviter la modification accidentelle de la signature de la méthode. Supposons que la méthode 'func' est définie dans la classe de base. La méthode accepte une variable de type int comme argument :

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

Ensuite, la méthode est dérivée dans la classe fille :

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

Cependant, le type de l'argument a été changé par erreur de int en short. De fait, ce n'est pas une dérivation de méthode mais une surcharge de méthode. Conformément à l'algorithme de définition des fonctions surchargées, le compilateur peut dans certains cas choisir une méthode définie dans la classe de base au lieu de la méthode dérivée.

Pour éviter ce genre d'erreurs, vous devriez ajouter explicitement le modificateur 'override' à la méthode que vous souhaitez dériver.

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

Si la signature de la métode est changée pendant la dérivation, le compilateur ne pourra pas trouver une méthode avec la même signature dans la classe parente, et il retournera une erreur de compilation :

La méthode 'CBar::func' est déclarée avec le spécificateur 'override' mais ne dérive d'aucune méthode de la classe de base

Modificateur final #

Le modificateur 'final' effectue l'inverse – il interdit tout dérivation de la méthode dans les classes filles. Si l'implémentation d'une méthode est suffisante et complète, déclarez cette méthode avec le modificateur 'final' pour être sûr qu'elle ne sera pas modifiée ultérieurement.

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

Si vous essayez de dériver une méthode ayant le modificateur 'final' comme montré dans l'exemple ci-dessus, le compilateur retournera une erreur :

La méthode 'CFoo::func' déclarée comme 'final' ne peut pas être dérivée par 'CBar::func'
voir la déclaration de 'CFoo::func'

Voir aussi

Bibliothèque standard