English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Когда нужно использовать указатели в MQL5

Когда нужно использовать указатели в MQL5

MetaTrader 5Примеры | 25 марта 2010, 11:25
7 026 39
MetaQuotes
MetaQuotes

Введение

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

Для автоматического создания объекта достаточно просто объявить переменную соответствующего класса - система автоматически создаст и проинициализирует его. Для динамического создания объекта необходимо явно использовать оператор new применительно к указателю объекта.

Но в чем разница между автоматически и динамически созданными объектами, и когда нам нужно обязательно использовать указатель, а когда достаточно ограничиться автоматическим созданием объектов? Этой теме и посвящена данная  статья. Но прежде чем перейти к непосредственной теме статьи, обсудим возможные опасности при работе с объектами и рассмотрим пути их устранения.

Критическая ошибка при обращении к некорректному указателю

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

//+------------------------------------------------------------------+
//|                                             GetCriticalError.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| простой класс                                                    |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string           m_message;
public:
                   CHello(){m_message="Начинаем...";}
   string           GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   Print(pstatus.GetMessage());
   Print(__FUNCTION__," Функция OnInit() отработала");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }

Переменная pstatus является указателем объекта, но сам объект мы преднамеренно "забыли" создать оператором new. При попытке запустить этого советника на графике EURUSD видим закономерный итог - эксперт был сразу же выгружен еще на стадии выполнения функции OnInit(). Сообщения об этом отображены в Журнале.

14:46:17    Expert GetCriticalError(EURUSD,H1) loaded successfully
14:46:18    Initializing of GetCriticalError (EURUSD,H1) failed
14:46:18    Expert GetCriticalError (EURUSD,H1) removed

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

Проверка указателя перед его использованием

Можно ли было избежать критического завершения программы? Да, конечно! Достаточно вставить проверку указателя объекта перед его использованием. Немного изменим пример, добавив в него функцию PrintStatus:

//+------------------------------------------------------------------+
//| выводим сообщение из объекта типа CHello                         |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," переменная 'obect' не инициализирована!");
   else Print(pobject.GetMessage());
  }

Теперь за вызов метода GetMessage() отвечает эта функция, в которую передается указатель переменной типа CHello. Но указатель предварительно проверяется на корректность функцией CheckPointer(). Добавим внешний параметр и сохраним код торгового советника как GetCriticalError_OnDemand.mq5.

//+------------------------------------------------------------------+
//|                                    GetCriticalError_OnDemand.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"

input bool GetStop=false;// Получить критическую ошибку
//+------------------------------------------------------------------+
//| простой класс                                                    |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Начинаем...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| выводим сообщение из объекта типа CHello                         |
//+------------------------------------------------------------------+
void PrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," переменная 'obect' не инициализирована!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- вызовем метод для показа статуса
   if(GetStop)
      pstatus.GetMessage();
   else
      PrintStatus(pstatus);
//--- выведем сообщение в случае успешной инициализации эксперта
   Print(__FUNCTION__," Функция OnInit() отработала");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Теперь мы можем по желанию запустить советника двумя способами:

  1. с получением критической ошибки (GetStop=true)
  2. без получения ошибки, но с выводом сообщения о некорректном указателе (GetStop=false)

По умолчанию эксперт выполняется успешно и выводит в журнал "Эксперты" подобное сообщение.

GetCriticalError_OnDemand (EURUSD,H1)    15:01:57    PrintStatus  переменная 'obect' не инициализирована!
GetCriticalError_OnDemand (EURUSD,H1)    15:01:57    OnInit  Функция OnInit() отработала

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

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

Передача неинициализированного объекта по ссылке

Что будет, если передать в функцию в качестве входного параметра неинициализированный объект? То есть не указатель объекта, а сам объект по ссылке. Ведь чаще всего объекты как сложные типы данных передаются по ссылке с использованием амперсанда. Перепишем немного код советника GetCriticalError_OnDemand.mq5, а именно: функцию PrintStatus() переименуем и запишем ее тело немного по иному.

//+------------------------------------------------------------------+
//| выводим сообщение из объекта типа CHello                         |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," переменная 'obect' не инициализирована!");
   else Print(object.GetMessage());
  }

Вся разница заключается в том, что теперь в качестве входного параметра передается не указатель объекта типа CClassHello, а сама переменная этого типа, и передается она по ссылке. Сохраним новый вариант эксперта под именем GetCriticalError_Unsafe.mq5.

//+------------------------------------------------------------------+
//|                                      GetCriticalError_Unsafe.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"

input bool GetStop=false;// Получить критическую ошибку
//+------------------------------------------------------------------+
//| простой класс                                                    |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Начинаем...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| выводим сообщение из объекта типа CHello                         |
//+------------------------------------------------------------------+
void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," переменная 'object' не инициализирована!");
   else Print(object.GetMessage());
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- вызовем метод для показа статуса
   if(GetStop)
      pstatus.GetMessage();
   else
      UnsafePrintStatus(pstatus);
//--- выведем сообщение в случае успешной инициализации эксперта
   Print(__FUNCTION__," Функция OnInit() отработала");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+

Если присмотреться, то можно увидеть, что вся разница между экспертами GetCriticalError_OnDemand.mq5 и GetCriticalError_Unsafe.mq5 заключается в способе передачи параметра внутрь функции. В первом случае передается указатель объекта, а во втором случае передается сам объект по ссылке. В обоих случаях перед использованием объекта/указателя объекта производится проверка корректности указателя.

Значит ли это, что эксперты будут выполняться одинаково? Нет, это не так! Запустим эксперта с параметром GetStop=false  и опять получим критическую ошибку. Дело в том, что если объект передается по ссылке, то критическая ошибка возникает еще на стадии вызова функции, в которую передается неинициализированный объект. В этом можно убедиться, если запустить скрипт в режиме отладки прямо из редактора MetaEditor по кнопке F5.

Чтобы не ставить вручную точку останова внутри функции, изменим ее, добавив программную точку останова DebugBreak():

void UnsafePrintStatus(CHello &object)
  {
   DebugBreak();
   if(CheckPointer(GetPointer(object))==POINTER_INVALID)
      Print(__FUNCTION__," переменная 'object' не инициализирована!");
   else Print(object.GetMessage());
  }

В режиме отладки эксперт будет выгружен раньше, чем выполнение дойдет до вызова DebugBreak(). Каким же образом можно обезопасить свой на тот случай, если в функцию будет передан неинициализированный объект по ссылке. Ответ прост - используем перегрузку функций.

Если указатель неинициализированного объекта будет передан в качестве параметра как объект по ссылке, то это приведет к критической ошибке и остановке работы mql5-программы

Перегрузка функции для безопасной работы кода

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

Реализуем метод  UnsafePrintStatus() с использованием перегрузки, напишем две версии функции - одну с использованием передачи указателя объекта, вместо самого объекта, а вторую с передачей параметра по ссылке. Назовем эти функции заново именем PrintStatus, так как это уже не будет потенциально опасной реализацией.

//+------------------------------------------------------------------+
//| безопасный вывод сообщения из указателя объекта типа CHello      |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," переменная 'object' не инициализирована!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| вывод сообщения из объекта типа CHello по ссылке                 |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello &object)
  {
   DebugBreak();
   CHello *p=GetPointer(object);
   SafePrintStatus(p);
  }

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

Мы можем записать безопасную версию функции еще короче, вместо

void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   CHello *p=GetPointer(pobject);
   SafePrintStatus(p);
  }

сделаем более компактный вариант.

void SafePrintStatus(CHello &object)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(object));
  }

Обе версии равноправны. Сохраним последнюю версию под именем GetCriticalError_Safe.mq5.

//+------------------------------------------------------------------+
//|                                        GetCriticalError_Safe.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"

input bool GetStop=false;// Получить критическую ошибку
//+------------------------------------------------------------------+
//| простой класс                                                    |
//+------------------------------------------------------------------+
class CHello
  {
private:
   string            m_message;
public:
                     CHello(){m_message="Начинаем...";}
   string            GetMessage(){return(m_message);}
  };
//---
CHello *pstatus;
//+------------------------------------------------------------------+
//| безопасный вывод сообщения из указателя объекта типа CHello      |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello *pobject)
  {
   if(CheckPointer(pobject)==POINTER_INVALID)
      Print(__FUNCTION__," переменная 'obect' не инициализирована!");
   else Print(pobject.GetMessage());
  }
//+------------------------------------------------------------------+
//| вывод сообщения из объекта типа CHello по ссылке                 |
//+------------------------------------------------------------------+
void SafePrintStatus(CHello &pobject)
  {
   DebugBreak();
   SafePrintStatus(GetPointer(pobject));
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- вызовем метод для показа статуса
   if(GetStop)
      pstatus.GetMessage();
   else
      SafePrintStatus(pstatus);
//--- выведем сообщение в случае успешной инициализации эксперта
   Print(__FUNCTION__," Функция OnInit() отработала");
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+
Данный пример показывает, что если необходима функция, которая принимает в качестве параметра объект по ссылке, то лучше написать сначала безопасную функцию fucnc(Object *param) которая принимает указатель объекта, а затем добавить перегруженную функцию fucnc(Object &param) , которая принимает объект по ссылке. И в этой второй функции получаем указатель объекта с помощью функции GetPointer() с последующим вызовом первой функции fucnc(Object *param) .

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

Теперь, когда мы наконец-то научились правильно обращаться с объектами, передаваемыми в качестве параметров в функцию, настало время все-таки выяснить

Когда нужны указатели?

Указатели объектов позволяют гибко управлять процессом создания и уничтожения объектов, и позволяют  создавать еще более сложнее объекты для получения еще большей абстракции. Это делает программу еще более гибкой.

Связанный список

Но в некоторых случаях нужен иной способ организации данных, один из них - связанный список. Класс связанного списка CList предоставляется в  Стандартной библиотеке, здесь мы только приведем собственные примеры. Связанный список означает, что каждый элемент списка имеет связь со следующим (next) и предыдущим (prev) элементами, если они есть. Для организации такой связи удобно использовать указатели объектов элементов списка (ListItem).

Создадим класс, который будет представлять элемент списка, например такой:

//+------------------------------------------------------------------+
//| элемент связанного списка                                        |
//+------------------------------------------------------------------+
class CListItem
  {
private:
   int               m_ID;
   CListItem        *m_next;
   CListItem        *m_prev;
public:
                    ~CListItem();
   void              setID(int id){m_ID=id;}
   int               getID(){return(m_ID);}
   void              next(CListItem *item){m_next=item;}
   void              prev(CListItem *item){m_prev=item;}
   CListItem*        next(){return(m_next);}
   CListItem*        prev(){return(m_prev);}
  };

Сам список организуем в отдельном классе:

//+------------------------------------------------------------------+
//| связанный список                                                 |
//+------------------------------------------------------------------+
class CList
  {
private:
   int               m_counter;
   CListItem        *m_first;
public:
                     CList(){m_counter=0;}
                    ~CList();
   void              addItem(CListItem *item);
   int               size(){return(m_counter);}
  };

Класс CList содержит указатель первого элемента списка m_first, доступ к остальным элементам списка всегда возможен через функции next() и prev() класса CListItem(). Главный интерес в классе CList представляют две функции. Первая, это добавление нового элемента в список.

//+------------------------------------------------------------------+
//| добавление элемента в список                                     |
//+------------------------------------------------------------------+
CList::addItem(CListItem *item)
  {
//--- сначала проверим корректность переданного указателя
   if(CheckPointer(item)==POINTER_INVALID) return;
//--- увеличим счетчик элементов в списке
   m_counter++;
//--- если элементов в списке еще нет
   if(CheckPointer(m_first)!=POINTER_DYNAMIC)
     {
      m_first=item;
     }
   else
     {
      //--- установим для item указатель предыдущего объекта
      m_first.prev(item);
      //--- запомним указатель текущего первого элемента
      CListItem *p=m_first;
      //--- поставим на место первого элемента входящий item
      m_first=item;
      //--- установим для первого элемента списка указатель следующего объекта
      m_first.next(p);
     }
  }

Каждый добавляемый в список элемент становится первым элементов списка, указатель предыдущего первого элемента запоминается в поле m_next. Таким образом, элемент, который был добавлен в список первым, окажется в итоге в самом конце списка. Элемент, который был добавлен последним, окажется самым первым, и его указатель хранится в переменной m_first.

Вторая интересная функция - это деструктор ~ CList(). Он вызывается при уничтожении списка, и должен обеспечивать правильное уничтожение элементов, входящих в список. Это делается очень просто:

//+------------------------------------------------------------------+
//| деструктор списка                                                |
//+------------------------------------------------------------------+
CList::~CList(void)
  {
   int ID=m_first.getID();
   if(CheckPointer(m_first)==POINTER_DYNAMIC) delete(m_first);
   Print(__FUNCTION__," Уничтожен первый элемент списка с ID=",ID);
  }

Как видно из кода, если m_first содержит корректный указатель элемента списка, то производится удаление только этого первого элемента списка. Все остальные элементы списка удаляются лавинообразно благодаря тому, что деструктор класса CListItem() в свою очередь правильно производит деинициализацию объекта.

//+------------------------------------------------------------------+
//|  деструктор элемента списка                                      |
//+------------------------------------------------------------------+
CListItem::~CListItem(void)
  {
   if(CheckPointer(m_next)==POINTER_DYNAMIC)
     {
      delete(m_next);
      Print(__FUNCTION__," Уничтожаем элемент с ID=",m_ID);
     }
   else
      Print(__FUNCTION__," Для элемента ",m_ID," не задан next");

  }

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

Работа списка продемонстрирована в скрипте SampleList.mq5. В его функции OnStart() содержится объявление списка list, который является переменной типа CList. Этот список будет создан и инициализирован автоматически. Наполнение же списка производится в списке, и каждый элемент списка сначала создается динамически оператором new, а затем добавляется в список.

void OnStart()
  {
//---
   CList list;
   for(int i=0;i<7;i++)
     {
      CListItem *item=new CListItem;
      item.setID(i);
      list.addItem(item);
     }
     Print("В списке",list.size(),"элементов");
  }

Запустите скрипт на выполнение и получите примерно такой вывод в журнал "Эксперты"

2010.03.18 11:22:05    SampleList (EURUSD,H1)    CList::~CList  Уничтожен первый элемент списка с ID = 6
2010.03.18 11:22:05    SampleList (EURUSD,H1)    CListItem::~CListItem  Уничтожаем элемент с ID = 6
2010.03.18 11:22:05    SampleList (EURUSD,H1)    CListItem::~CListItem  Уничтожаем элемент с ID = 5
2010.03.18 11:22:05    SampleList (EURUSD,H1)    CListItem::~CListItem  Уничтожаем элемент с ID = 4
2010.03.18 11:22:05    SampleList (EURUSD,H1)    CListItem::~CListItem  Уничтожаем элемент с ID = 3
2010.03.18 11:22:05    SampleList (EURUSD,H1)    CListItem::~CListItem  Уничтожаем элемент с ID = 2
2010.03.18 11:22:05    SampleList (EURUSD,H1)    CListItem::~CListItem  Уничтожаем элемент с ID = 1
2010.03.18 11:22:05    SampleList (EURUSD,H1)    CListItem::~CListItem  Для элемента 0  не задан next
2010.03.18 11:22:05    SampleList (EURUSD,H1)    В списке 7 элементов

Если вы внимательно изучили код, то для вас не будет неожиданностью, что элемент с ID=0 не имеет следующего за ним элемента. Но мы рассмотрели только одну причину, когда использование указателей делает написание программ удобным и читабельным. Есть и вторая причина, и называется она

Полиморфизм

Часто требуется реализовать одинаковый функционал для разных объектов, но относящихся к одному типу. Например, имеются простые объекты Линия, Треугольник, Прямоугольник и Круг. Несмотря на то, что выглядят они по разному, они имеют общее свойство - их можно нарисовать. Создадим базовый класс CShape, от которого произведем потомков для каждого вида геометрической фигуры.


Базовый класс и его потомки в учебных целях имеют минимальный функционал.

//+------------------------------------------------------------------+
//| базовый класс фигура                                             |
//+------------------------------------------------------------------+
class CShape
  {
private:
   int               m_type;
public:
                     CShape(){m_type=0;}
   void              Draw();
   string            getTypeName(){return("Shape");}
  };
//+------------------------------------------------------------------+
//| объект Линия                                                     |
//+------------------------------------------------------------------+
class CLine:public CShape
  {
private:
   int               m_type;
public:
                     CLine(){m_type=1;}
   void              Draw();
   string            getTypeName(){return("Line");}
  };
//+------------------------------------------------------------------+
//| объект Треугольник                                               |
//+------------------------------------------------------------------+
class CTriangle:public CShape
  {
private:
   int               m_type;
public:
                     CTriangle(){m_type=2;}
   void              Draw();
   string            getTypeName(){return("Triangle");}
  };
//+------------------------------------------------------------------+
//| объект Прямоугольник                                             |
//+------------------------------------------------------------------+
class CRectangle:public CShape
  {
private:
   int               m_type;
public:
                     CRectangle(){m_type=3;}
   void              Draw();
   string            getTypeName(){return("Rectangle");}
  };
//+------------------------------------------------------------------+
//| объект Круг                                                      |
//+------------------------------------------------------------------+
class CCircle:public CShape
  {
private:
   int               m_type;
public:
                     CCircle(){m_type=4;}
   void              Draw();
   string            getTypeName(){return("Circle");}
  };

Базовый класс CShape содержит две функции, которые затем переопределяются в его потомках - это Draw() и getTypeName(). Функция Draw() предназначена для отрисовки фигуры, а функция getTypeName() возвращает строковое описание фигуры.

Создадим массив *shapes[], который содержит указатели базового типа CShape, и зададим ему значения указателей разных классов.

void OnStart()
  {
//--- массив указателей объекта типа CShape
   CShape *shapes[];
//--- зададим размер массива   
   ArrayResize(shapes,5);
//--- зададим значения уазателям в массиве
   shapes[0]=new CShape;
   shapes[1]=(CShape)new CLine;
   shapes[2]=(CShape)new CTriangle;
   shapes[3]=(CShape)new CRectangle;
   shapes[4]=(CShape)new CCircle;
//--- выведем для каждого элемента в массиве его тип
   for(int i=0;i<5;i++)
     {
      Print(i,shapes[i].getTypeName());
     }
//--- уничтожим объекты в массиве
   for(int i=0;i<5;i++) delete(shapes[i]);
  }

В цикле for() производится вызов метода getTypeName() для каждого элемента массива *shapes[]. Несмотря на то, что каждый производный класс имеют свою реализацию функции getTypeName(), для каждого объекта в списке вызывается функция базового класса CShape, что может удивить в первый раз.

2010.03.18 14:06:18    DemoPolymorphism (EURUSD,H1)    4 Shape
2010.03.18 14:06:18    DemoPolymorphism (EURUSD,H1)    3 Shape
2010.03.18 14:06:18    DemoPolymorphism (EURUSD,H1)    2 Shape
2010.03.18 14:06:18    DemoPolymorphism (EURUSD,H1)    1 Shape
2010.03.18 14:06:18    DemoPolymorphism (EURUSD,H1)    0 Shape

Объяснение этому факту такое: массив *shapes[] объявлен как массив указателей переменных типа CShape, и поэтому для каждого его объекта вызывается именно метод getTypeName() базового класса, даже несмотря на то, что потомок имеет иную реализацию. Для того, чтобы в момент выполнения программы была вызвана функция getTypeName(), соответствующая реальному типу объекта, необходимо в базовом классе определить эту функцию как виртуальную.

Добавим в объявлении родительского класса CShape ключевое слово virtual для функции getTypeName(). 

class CShape
  {
private:
   int               m_type;
public:
                     CShape(){m_type=0;}
   void              Draw();
   virtual string    getTypeName(){return("Shape");}
  };

и снова запустим скрипт. Теперь результаты соответствуют ожидаемым

2010.03.18 15:01:11    DemoPolymorphism (EURUSD,H1)    4 Circle
2010.03.18 15:01:11    DemoPolymorphism (EURUSD,H1)    3 Rectangle
2010.03.18 15:01:11    DemoPolymorphism (EURUSD,H1)    2 Triangle
2010.03.18 15:01:11    DemoPolymorphism (EURUSD,H1)    1 Line
2010.03.18 15:01:11    DemoPolymorphism (EURUSD,H1)    0 Shape

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

Заключение

Итак, подведем итоги. Создание и уничтожение объектов в MQL5 производится автоматически, поэтому пользуйтесь указателями только в том случае, если они действительно необходимы, и  если вы хорошо понимаете как с ними работать.

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

И последнее: указатели в MQL5 не являются указателями в том понимании, как они используются в C++. Указатели объектов MQL5 не имеет смысла передавать в качестве параметра в DLL.

Последние комментарии | Перейти к обсуждению на форуме трейдеров (39)
MetaQuotes
Renat Fatkhullin | 6 нояб. 2014 в 09:55
TheXpert:
Блин, и вы после этого про безопасность языка говорите?

Вероятно, вы думаете, что можно свободно приводить к чему угодно как в С/С++.

Это не так и с безопасностью все в порядке.

Anatoli Kazharski
Anatoli Kazharski | 6 нояб. 2014 в 12:41
Renat:

Вероятно, вы думаете, что можно свободно приводить к чему угодно как в С/С++.

Это не так и с безопасностью все в порядке.

Случайно получил вот такую ошибку, что похоже подтверждает Ваши слова. ) 

2014.11.06 14:33:36.588 OOP_Test (EURCHF,M5)   incorrect casting of pointers in 'Test1.mqh' (931,13)
TheXpert
TheXpert | 6 нояб. 2014 в 14:36
Renat:

Это не так и с безопасностью все в порядке.

Нет, нельзя нормально сделать проверку dynamic_cast в время компиляции.
MetaQuotes
Renat Fatkhullin | 6 нояб. 2014 в 14:48
TheXpert:
Нет, нельзя нормально сделать проверку dynamic_cast в время компиляции.

Комментом выше показ результат проверки кастинга в рантайме.

Он у нас очень жесткий, работает на RTTI механизме, так как мы точно знаем кто есть кто при приведениях.

TheXpert
TheXpert | 6 нояб. 2014 в 17:07
Renat:

Комментом выше показ результат проверки кастинга в рантайме.

Упс... попутал. Подумал чего-то что это компилятора. Тогда беру свои слова обратно.
Создание индикатора с возможностями графического управления Создание индикатора с возможностями графического управления
Те, кто более-менее знаком с рыночными настроениями не понаслышке, знает индикатор MACD, или полное название Схождение Расхождение Скользящих Средних, и знает его как мощный инструмент анализа движения цены, которым пользуются трейдеры с первых моментов появления методов компьютерного анализа. В данной статье мы рассмотрим возможные модификации MACD и реализуем их в одном индикаторе с возможностью графического переключения между модификациями.
Построение излучений индикаторов в MQL5 Построение излучений индикаторов в MQL5
В статье рассматриваются излучения индикаторов - новое направление исследования рынка. Принцип построения излучений основан на пересечении линий различных индикаторов - с каждым тиком на графике появляются всё новые и новые точки с различным цветом и формой. Они образуют многочисленные скопления в виде туманностей, облаков, дорожек, линий, дуг и т.п. Эти характерные области точек помогают обнаружить невидимые пружины и силы, которые влияют на движение рыночных цен.
Стили рисования в MQL5 Стили рисования в MQL5
В MQL4 есть 6 типов графического отображения индикаторов, а MQL5 доступно уже 18 стилей рисования. Поэтому, возможно, стоит написать статью о стилях рисования в MQL5. В данной статье мы рассмотрим подробности работы со стилями графического отображения индикаторов. Кроме того, мы создадим индикатор для иллюстрации всех этих стилей и уточним особенности графических построений.
Практическая реализация цифровых фильтров на MQL5 для начинающих Практическая реализация цифровых фильтров на MQL5 для начинающих
Идее цифровой фильтрации сигналов посвящаются достаточно объёмные темы обсуждения на форумах по построению торговых систем. В этой статье автор знакомит с процессом превращения кода более простого индикатора SMA из своей статьи "Пользовательские индикаторы в MQL5 для начинающих" в код гораздо более сложного универсального цифрового фильтра. В ней также изложены простейшие приёмы замены текста в коде и методика получения простейших навыков по исправлению ошибок программирования.