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

 

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

Вот код для проверки:

class CTest1
{
protected:
   int id; // Только не private
   
public:
   CTest1() {id = MathRand(); Print(__FUNCSIG__," = CTest1, id = ",id);}
   ~CTest1() {Print(__FUNCSIG__," = CTest1, id = ",id);}
   
   void func1() {Print(__FUNCSIG__," = CTest1, id = ",id);}
   virtual void func2() {Print(__FUNCSIG__," = CTest1, id = ",id);}
   virtual void func3() {Print(__FUNCSIG__," = CTest1, id = ",id," typename = ",typename(this));}
};

class CTest2 : public CTest1
{
protected:
   int id2; // Только не private

public:
   CTest2() {id2 = MathRand(); Print(__FUNCSIG__," = CTest2, id = ",id,", id2 = ",id2);}
   ~CTest2() {Print(__FUNCSIG__," = CTest2, id = ",id,", id2 = ",id2);}
   
   void func1() {Print(__FUNCSIG__," = CTest2, id = ",id,", id2 = ",id2);}
   virtual void func2() {Print(__FUNCSIG__," = CTest2, id = ",id,", id2 = ",id2);}
};

void OnStart()
{
   Print("START");
   MathSrand(GetTickCount());
   
   Print("CTest1 *t1");
   CTest1 *t1 = new CTest1();
   t1.func1();
   t1.func2();
   t1.func3();
   delete t1;
   
   Print("CTest2 *t2");
   CTest2 *t2 = new CTest2();
   t2.func1();
   t2.func2();
   t2.func3();
   delete t2;

   /*Print("CTest1 *t11 = new CTest2()");
   CTest1 *t11 = new CTest2();
   t11.func1();
   t11.func2();
   delete t11;*/
   
   /*Print("CTest2 *t22 = new CTest1()");
   CTest2 *t22 = new CTest1();
   t22.func1();
   t22.func2();
   delete t22;*/
   
   Print("virtual funcs START");
   
   Print("CTest2 tv2(); CTest1 *tv1 = (CTest1 *)tv2");
   CTest2 tv2();
   CTest1 *tv1 = (CTest1 *)tv2;
   tv2.func1();
   tv2.func2();
   tv2.func3();
   tv1.func1();
   tv1.func2(); Print(" <=== Вот здесь подмена");
   tv1.func3();
   //Print("points tv1=",EnumToString(CheckPointer(tv1)));
   //delete tv2;
   //Print("points tv1=",EnumToString(CheckPointer(tv1)));
   delete tv1;
   Print("points tv1=",EnumToString(CheckPointer(tv1)));
   Print("Здесь указатель на локальную переменную, которая уничтожится сама.");
   Print("Delete тут бессилен, а вот указатель стоит через CheckPointer(tv1) контролировать.");

   Print("CTest2 *tvv2 = new CTest2(); CTest1 *tvv1 = (CTest1 *)tvv2;");
   CTest2 *tvv2 = new CTest2();
   CTest1 *tvv1 = (CTest1 *)tvv2;
   tvv2.func1();
   tvv2.func2();
   tvv2.func3();
   tvv1.func1();
   tvv1.func2(); Print(" <=== Вот здесь подмена");
   tvv1.func3();
   Print("points tvv1=",EnumToString(CheckPointer(tvv1))," tvv2=",EnumToString(CheckPointer(tvv2)));
   delete tvv2;
   delete tvv1;
   Print("points tvv1=",EnumToString(CheckPointer(tvv1))," tvv2=",EnumToString(CheckPointer(tvv2)));
   Print("Неважно, какой именно указатель в этом случае скармливать delete, но нужно это сделать 1 раз");
   
   Print("virtual funcs FINISH");

   Print("CTest1 t111();");
   CTest1 t111();
   t111.func1();
   t111.func2();
   t111.func3();
   //delete t111;

   Print("CTest2 t222()");
   CTest2 t222();
   t222.func1();
   t222.func2();
   t222.func3();
   //delete t222;
   
   Print("FINISH");
}

По результатам видно (я в коде строки пометил), что реальное замещение (подмена) происходит только при вызове через указатель, а всё остальное - это наследственность.

Выложил, как пример, но может есть ещё неучтённые мной возможности?

Была мысль, что в func3() typename(this) вернет имя наследника, но это оказалось лишь мечтой...

Документация по MQL5: Основы языка / Объектно-ориентированное программирование / Виртуальные функции
Документация по MQL5: Основы языка / Объектно-ориентированное программирование / Виртуальные функции
  • www.mql5.com
Основы языка / Объектно-ориентированное программирование / Виртуальные функции - Документация по MQL5
 
Виртуальные функции вызываются через специальную таблицу, которая назначается объекту в конструкторе, в ваше примере виртуальных таблиц две:

CTest1CTest2
CTest1::~CTest1
CTest2::~CTest2()
CTest1::func2CTest2::func2
CTest1::func3CTest1::func3

Далее, к какому бы типу объекта Вы не приводили указатель, закреплённая таблица за объектом не меняется.
Все вызовы виртуальных функций, на самом деле стоит читать как "вызвать функцию из таблицы под номером N"

Почитайте про полиморфизм.

Для лучшего понимая можно привести следующий пример:
class CAnimal
  {
public:
   virtual string Sound() { return(""); }
  };

class CCat : public CAnimal
  {
public:
   virtual string Sound() { return("Мяу"); }
  };

class CDog : public CAnimal
  {
public:
   virtual string Sound() { return("Гав"); }
  };
  
void OnStart()
  {
   CAnimal *animal[2];

   animal[0]=new CCat();
   animal[1]=new CDog();
//--
   for(int i=0;i<ArraySize(animal);i++)
      Print(animal[i].Sound());
//---
   delete animal[0];
   delete animal[1];
  }
Причина обращения: