Funzioni Virtuali

La parola chiave virtuale è l'identificatore di funzione, che fornisce un meccanismo per selezionare dinamicamente in fase di esecuzione un appropriato membro-di-funzione tra le funzioni delle classi di base e derivate. Le strutture non possono avere funzioni virtuali. Esse possono essere utilizzate per cambiare le dichiarazioni solo per i membri della funzione.

La funzione virtuale, come una funzione ordinaria, deve avere un corpo eseguibile. Quando viene chiamata, la sua semantica è la stessa di quella delle altre funzioni.

Una funzione virtuale può essere sottoposta a override in una classe derivata. La scelta di quale definizione di funzione dovrebbe essere chiamata per una funzione virtuale, è fatta in modo dinamico (in fase di esecuzione). Un caso tipico è quando una classe base contiene una funzione virtuale, e classi derivate hanno le loro versioni di questa funzione.

Il puntatore alla classe base può indicare sia un oggetto di classe base che l'oggetto di una classe derivata. La scelta del membro-funzione da chiamare verrà eseguita in fase di esecuzione e dipende dal tipo di oggetto, non dal tipo di puntatore. Se non vi sono membri di un tipo derivato, la funzione virtuale della classe base viene utilizzata per impostazione predefinita.

I Distruttori sono sempre virtuali, indipendentemente dal fatto che siano dichiarati o meno con la parola chiave virtual.

Prendiamo in considerazione l'uso di funzioni virtuali sull'esempio di MT5_Tetris.mq5. La classe base CTetrisShape con la funzione virtuale Dra è definita nel file include 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[]);
  };

Inoltre, per ciascuna classe derivata, questa funzione è implementata in conformità con le caratteristiche di una classe discendente. Ad esempio, la prima forma CTetrisShape1 ha la propria implementazione della funzione Draw():

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- disegno forma
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- orizzontale
         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
        {
         //--- 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 forma Square è descritta dalla classe CTetrisShape6 ha la propria implementazione del metodo Draw():

class CTetrisShape6 : public CTetrisShape
  {
public:
   //--- Disegno forma
   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);
        }
     }
  };

A seconda della classe a cui appartiene l'oggetto creato, essa chiama la funzione virtuale di questa o quella classe derivata.

void CTetrisField::NewShape()
  {
//--- creazione di una delle 7 forme possibili a caso 
   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;
     }
//--- draw
   m_shape.Draw();
//---
  }

Modifier 'override' #

Il modificatore 'overrire' significa che la funzione dichiarata deve eseguire l'override("scavalco) del metodo di una classe genitore. L'utilizzo di questo metodo consente di evitare errori di primaria importanza, ad esempio, consente di evitare la modifica accidentale della firma del metodo. Supponiamo che, il metodo 'func' è definito nella classe base. Il metodo accetta una variabile int come argomento:

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

In seguito, il metodo viene scavalcato nella classe figlia:

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

Tuttavia, il tipo di argomento è erroneamente cambiato da int a short. In realtà, questo non è l'overriding del metodo, ma è l'overloading del metodo. Agendo in accordo all algoritmo definente la funzione overloadata, Il compilatore può in alcune situazioni scegliere un metodo definito nella classe base invece del metodo overridato.

Al fine di evitare tali errori, si dovrebbe aggiungere in modo esplicito il modificatore 'override' per il metodo che si desidera overridare.

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

Se la firma del metodo viene modificata durante l'override, il compilatore non sarà in grado di trovare un metodo con la stessa firma nella classe padre, e restituituirà un errore di compilazione:

'CBar::func' medoto è dichiarato con lo specificatore 'override' ma non fa l'override di nessun metodo della classe base

Modificatore 'final' #

Il modificatore 'final' fa il contrario — vieta il metodo prevalente alla classi figlie. Se un'implementazione metodo è sufficiente e completa del tutto, dichiarare questo metodo con il modificatore 'final' in modo da assicurarsi che non sarà modificato successivamente.

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

Se si tenta di eseguire l'override un metodo con il modificatore 'final', come mostrato nell'esempio precedente, il compilatore restituirà un errore:

'CFoo::func' metodo dichiarato come 'final' non può essere overridato da 'CBar::func'
vedere la dichiarazione di 'CFoo::func'

Vedi anche

Standard Library