Funções Virtuais

A palavra-chave virtual é o especificador de função que fornece um mecanismo para selecionar dinamicamente em tempo de execução uma função-membro apropriada entre as funções de classes base e derivadas. Estrutura não podem ter funções virtuais. Pode ser usado para alterar as declarações de funções-membro somente.

A função virtual, assim como uma função comum, deve ter um corpo executável. Ao ser chamada, sua semântica é a mesma que das outras funções.

Uma função virtual pode ser sobreposta (overridden) em um classe derivada. A escolha de qual definição de função deve ser chamada para uma função virtual é feita dinamicamente (em tempo de execução). Um caso típico é quando uma classe base contém uma função virtual, e as classes derivadas têm sua própria versão desta função.

O ponteiro para a classe base pode indicar tanto um objeto da classe base quanto um objeto de uma classe derivada. A escolha da função-membro a ser chamada será executada em tempo de execução e dependerá do tipo do objeto, não do tipo do ponteiro. Se não houver nenhum membro de um tipo derivado, a função virtual da classe base é usada por default.

Destrutores são sempre virtuais, independentemente se elas estão declaradas com a palavra-chave virtual ou não.

Vamos considerar o uso de funções virtuais no exemplo do MT5_Tetris.mq5. A classe base CTetrisShape com a função virtual de desenhar é definjida na inclusão do arquivo MT5_TetisShape.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[]);
  };

Mais adiante, para cada classe derivada, esta função é implementada de acordo com as características da classe descendente. Por exemplo, a primeira forma CTetrisShape1 tem sua implementação própria da função Draw():

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- desenhando formato
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- horizontal
         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
        {
         //--- vertical
         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);
           }
        }
     }
  }

A forma Quadrado é descrita por meio da classe CTetrisShape6 e tem sua implementação própria do método Draw():

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

Dependendo da classe da qual o objeto criado pertence, é chamada a função virtual desta ou daquela classe derivada.

void CTetrisField::NewShape()
  {
//--- criando uma dos 7 possíveis formas aleatoriamente
   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;
     }
//--- desenhar
   m_shape.Draw();
//---
  }

Modificador override #

O modificador override indica que a função declarada deve substituir o método da classe pai. O uso deste modificador permite evitar erros durante as substituições, por exemplo, durante uma alteração aleatória na assinatura do método. Ou, por exemplo, na classe base, está definido o método func, ele é usado como argumento da variável do tipo int:

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

A seguir, o método é substituído na classe que está sendo herdada:

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

No entanto, no caso de haver um erro, o tipo de argumento é alterado de int para short. De fato, aqui acontece uma sobrecarga do método. Agindo em conformidade com o algoritmo de estabelecimento da função sobrecarregada, em determinadas situações, o compilador pode selecionar o método definido na classe base, em vez de escolher o método de substituição.

Para evitar esses erros, é necessário adicionar o modificador override ao método de substituição.

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

Se, durante a substituição, for alterada a assinatura do método, o compilador não conseguirá encontrar, na classe pai, o método com essa mesma assinatura e emitir o erro de compilação:

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

Modificador final #

Ao contrário do anterior, o modificador final proíbe a substituição do método em classes de herança. Se a implementação do método for auto-suficiente e concluída na sua totalidade, declare esse fato usando o modificador final, para ele não ser alterado em conseqüência.

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

Como no exemplo acima, ao tentar substituir o método usando o modificador final, o compilador irá emitir um erro:

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

Veja Também

Standard Library