Скачать MetaTrader 5

Виртуальные функции

Ключевое слово virtual служит спецификатором функции, который обеспечивает механизм для динамического выбора на этапе выполнения подходящей функции-члена среди функций базового и производного классов, структуры не могут иметь виртуальных функций. Оно может применяться для изменения объявлений только функций-членов.

Виртуальная функция, как и обычная функция, должна иметь исполняемое тело. При вызове семантика ее точно такая же, как и у остальных функций.

Виртуальная функция может замещаться в производном классе. Выбор того, какое определение функции вызвать для виртуальной функции, происходит динамически (на этапе выполнения). Типичный случай – когда базовый класс содержит виртуальную функцию, а производные классы имеют свои версии этой функции.

Указатель на базовый класс может указывать либо на объект базового класса, либо на объект производного класса. Выбор вызываемой функции-члена будет произведен на этапе выполнения и будет зависеть от типа объекта, а не от типа указателя. При отсутствии члена производного типа по умолчанию используется виртуальная функция базового класса.

Деструкторы всегда являются виртуальными, независимо от того, объявлены они с ключевым слово virtual или нет.

Рассмотрим использование виртуальных функций на примере программы MT5_Tetris.mq5. Во включаемом файле MT5_TetisShape.mqh определен базовый класс CTetrisShape с виртуальной функцией Draw (рисовать).

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

Далее для каждого производного класса эта функция реализована в соответствии с особенностями класса-потомка. Например, первая фигура CTetrisShape1 имеет свою реализацию функции Draw():

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- отрисовка фигуры
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- горизонтальная палка
         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
        {
         //--- вертикальная палка
         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);
           }
        }
     }
  }

Фигура квадрат описана классом CTetrisShape6 и имеет собственную реализацию метода Draw():

class CTetrisShape6 : public CTetrisShape
  {
public:
   //--- отрисовка фигуры
   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);
        }
     }
  };

В зависимости от того, объект какого класса создан, вызывается виртуальная функция того или иного производного класса.

void CTetrisField::NewShape()
  {
//--- случайным образом создаём одну из 7 возможных фигур
   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;
     }
//--- отрисовываем
   m_shape.Draw();
//---
  }

Модификатор override

Модификатор override означает, что объявляемая функция обязательно должна переопределить метод родительского класса. Использование этого модификатора позволяет избежать ошибок при переопределении, таких как случайное изменение сигнатуры метода. Например, в базовом классе определен метод func, принимающий в качестве аргумента переменную типа int:

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

Далее метод переопределяется в наследуемом классе:

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

Но по ошибке тип аргумента изменяется с int на short. Фактически, в этом случае уже происходит не переопределение, а перегрузка метода. Действуя в соответствии с алгоритмом определения перегруженной функции, в определенных ситуациях компилятор может выбрать метод, определенный в базовом классе, вместо переопределенного метода.

Чтобы избежать подобных ошибок, к переопределяемому методу следует явно добавлять модификатор override.

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

Если при переопределении будет изменена сигнатура метода, компилятор не сможет найти в родительском классе метод с точной такой же сигнатурой и выдаст ошибку компиляции:

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

Модификатор final

Модификатор final действует наоборот — он запрещает переопределение метода в классах-наследниках. Если реализация метода самодостаточна и полностью завершена, объявите его с модификатором final, чтобы он гарантированно не был изменен в последующем.

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

При попытке переопределения метода с модификатором final, как показано в примере выше, компилятор выдаст ошибку:

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

Смотри также

Стандартная библиотека


Обновлено: 2017.03.08