Вопросы по ООП в MQL5 - страница 97

 
Alexey Volchanskiy #:

В учебнике куча ошибок, например:

--------------

Для автоматического объекта мы получали указатель, поставив амперсанд перед его именем (в данном контексте, символ амперсанда является оператором "взятия адреса"). Так, в следующем примере переменная p указывает на автоматический объект s.

Shape s;        // автоматический объект
Shape *p = &s;  // указатель на тот же объект
------------------
Никакие адреса амперсанд в MQL4/5 не возвращает, это не С++. В MQL в угоду безопасности нет работы с адресами, как и в C# и многих других языках. Самое смешное, что строчкой выше автор правильно пишет об этой особенности.

Лично для меня это, сейчас вообще не важно. Но если я споткнусь в подобной ситуации, я всегда могу перечитать ваше высказывание…

 
Alexey Viktorov #:

То-есть это можно применить как замену списку объектов?

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

Я затрудняюсь ответить. У меня нет опыта широкого применения паттернов подобных примеру из учебника. Я крайне редко разрабатываю мультивалютные советники и еще не создавал крутые продвинутые GUI (возможно что-то подобное может найти применение для GUI). Ну и необходимости в чем-то подобном у меня тоже не было:

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Вопросы по ООП в MQL5

Stanislav Korotky, 2024.11.20 16:27

сообщение группой расчетных алгоритмов процента выполнения конкретной задачи в планировщик задач

Хорошо, что Станислав присоединился к обсуждению, он компетентен в таких вопросах, в отличии от меня.

 
Vladislav Boyko #:


Хорошо, что Станислав присоединился к обсуждению, он компетентен в таких вопросах, в отличии от меня.

В компетентности его я не сомневаюсь. Просто от критики мало пользы…

 
Stanislav Korotky #:
Предложенная модификация с "преобразованием ссылки в указатель" делает код менее защищенным, так как ссылка не может быть испорчена, а указатель может. Кроме того, в вызывающем операторе для получения указателя нужно писать амперсанд (который вам же и не нравится), а вот ссылку мы получаем простым написанием имени объекта.

Я упростил код для того, чтобы он выглядел более понятным и читаемым для человека, который только изучает ООП. Я не предлагал модификации, я пытался объяснить что делает ваш код, переписав его более простым и наглядным способом. Следующие 2 строки делают одно и то-же:

owner = &t;
owner = GetPointer(t);

Но GetPointer более читаемо выглядит и его легко найти в документации.

В документации по this тоже GetPointer.

https://www.mql5.com/ru/docs/basis/types/this

//+------------------------------------------------------------------+
//| возвращает собственный указатель                                 |
//+------------------------------------------------------------------+
CDemoClass *CDemoClass::getDemoClass(void)
  {
   return(GetPointer(this));
  }
 
Stanislav Korotky #:
Предложенная модификация с "преобразованием ссылки в указатель" делает код менее защищенным, так как ссылка не может быть испорчена, а указатель может.

Я уже писал, что я преследовал цель сделать код проще, чтобы сосредоточиться на вашем паттерне.

Но при желании испортить можно что угодно:

class Manager;

class Element
  {
   Manager* owner;
public:
            Element(Manager &t) : owner(&t) { }
  };

class Manager {};

void OnStart()
  {
   Manager* m = NULL;
   Element e1(m); // OK
   Element e2((Manager*)NULL); // OK
  }
 
Vladislav Boyko #:

Я упростил код для того, чтобы он выглядел более понятным и читаемым для человека, который только изучает ООП. Я не предлагал модификации, я пытался объяснить что делает ваш код, переписав его более простым и наглядным способом. Следующие 2 строки делают одно и то-же:

Но GetPointer более читаемо выглядит и его легко найти в документации.

В документации по this тоже GetPointer.

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

В общем спасибо Станиславу за такой учебник, за пример такой возможности, а вам спасибо за доходчивое объяснение.

На мой взгляд это говорит о настоящей пользе ООП. Всё что есть в СБ это пародия на ООП. Ну разве что, за некоторым исключением…

 
Stanislav Korotky #:
Один менеджер (одного типа, как в нашем примере) - это как раз самый частый случай, и он полон смыслов.

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

class Element
  {
public:
   string getMyName() const { return (typename(this)); }
   void   doMath();
  };

void Element::doMath(void)
  {
   const int N = 1000000;
   for(int i = 0; i < N; ++i)
      if(i % (N / 20) == 0)
         Manager::progressNotify(this, i * 100.0f / N);
  }

class Manager
  {
public:
   static void progressNotify(const Element &e, const float percent) { Print(e.getMyName(), "=", percent); }
  };

void OnStart()
  {
   Element arr[10];
   arr[5].doMath();
  }

И сам менеджер тоже теряет смысл. Его можно переименовать в ProgressNotifier и пускай там уведомляет себе.

Зачем делать громоздкий код для уведомлений? Для чего каждому элементу хранить копию указателя на менеджера, если менеджер только один на всю программу? Если что-то всегда будет только одно на всю программу, то его можно сделать полностью статическим и не морочить голову с хранением указателей.

Поэтому я и говорю, что ваш пример слишком абстрактный. Просто указатели ради указателей. Но, тем не менее, это не делает его плохим.

https://www.mql5.com/ru/book/oop/classes_and_interfaces/classes_pointers

void progressNotify(Element *e, const float percent)

Там ссылки достаточно для вызова методов.

 
Vladislav Boyko #:
Для чего каждому элементу хранить копию указателя на менеджера

Только если менеджеров может быть несколько экземпляров. Я не могу придумать другую причину для хранения копии указателя каждым элементом.

А для чего нужны несколько менеджеров, каждый из которых управляет собственным массивом элементов (которые, в свою очередь, могут общаться с оунером), лично мне придумать сложно. Это должно быть что-то большое, хитрое и масштабируемое. Собственно поэтому вы привели абстрактный пример (наверное) - что бы не добавлять в качестве примера большую программу.

 

Вообще, паттерн, при котором, объект владения владеет указателем на владельца, так себе решение, от лени я бы сказал. Тут, с ходу, две проблемы:

  1. Объект владения получает доступ ко всем публичным методам владельца. В принципе, бизнес-логики это фиолетово, а вот для тулзов большого проекта может иметь перспективы неприятных поисков багов.
  2. На момент разыменования указателя на владельца, он тупо может быть не валиден.
По хорошему, владелец должен предоставлять объектам владения, некие сущности для обратных запросов, которые будут это все обеспечивать.
 

Я опять к вам за помощью, за разъяснениями…

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

Вроде бы всё записывается и читается, но вот одна виртуальная функция почему-то не «виртуалится»😊

Вот код класса (он-же в прикреплённом файле)

/********************************************************************\
|                                                TestSaveLoadObj.mqh |
|                                            © 2025, Alexey Viktorov |
|                       https://www.mql5.com/ru/users/alexeyvik/news |
\********************************************************************/
#property copyright "© 2025, Alexey Viktorov"
#property link      "https://www.mql5.com/ru/users/alexeyvik/news"
//---
//#include <Object.mqh>        // Если разбанить эти две строки
//#include <Arrays\Array.mqh>  // то ничего не меняется.
#include <Arrays\ArrayObj.mqh>
CArrayObj             list;
//---
datetime startTime=D'2025.03.15'; // Дата, начиная с которой читаем историю сделок и ордеров
/********************************************************************\
\********************************************************************/
//class CTestClass : public CObject
class CTestClass : public CArrayObj
 {
private:
  //---
  string             m_fileName;
  string             m_symbol;
  long               m_posID;
  long               m_Type;
  ulong              m_Magic;
  ulong              m_TicketIn;
  ulong              m_TicketOut;
  double             m_priceIn;
  double             m_priceOut;
  double             m_posVolume;
public:
                     CTestClass(void);
                    ~CTestClass(void) {;}
  //---
  void               setSymbol(string symbol)                           { this.m_symbol = symbol;                           };
  void               setID(const long id)                               { this.m_posID=id;                                  };
  void               setType(const long type)                           { this.m_Type=type;                                 };
  void               setMagic(const ulong magic)                        { this.m_Magic=magic;                               };
  void               setTicketIn(const ulong ticket)                    { this.m_TicketIn=ticket;                           };
  void               setTicketOut(const ulong ticket)                   { this.m_TicketOut=ticket;                          };
  void               setPriceIn(const double price)                     { this.m_priceIn=price;                             };
  void               setPriceOut(const double price)                    { this.m_priceOut=price;                            };
  void               setVolume(const double volume)                     { this.m_posVolume=volume;                          };
  //---
  string             getSymbol(void)                              const { return this.m_symbol;                             };
  long               getID(void)                                  const { return this.m_posID;                              };
  long               getType(void)                                const { return this.m_Type;                               };
  ulong              getMagic(void)                               const { return this.m_Magic;                              };
  ulong              getTicketIn(void)                            const { return this.m_TicketIn;                           };
  ulong              getTicketOut(void)                           const { return this.m_TicketOut;                          };
  double             getPriceIn(void)                             const { return this.m_priceIn;                            };
  double             getPriceOut(void)                            const { return this.m_priceOut;                           };
  double             getVolume(void)                              const { return this.m_posVolume;                          };
  //---
  void               listCreate(long posID);
  void               fileWrite();
  void               fileRead(void);
  //---
private:
  //---
  virtual int        Compare(const CObject *node, const int mode = 0) const;
  template <typename T1>
  int                compare(T1 data1, T1 data2) const;
  virtual bool       Save(const int file_handle);
  virtual bool       Load(const int file_handle);
//public:
  virtual bool       CreateElement(const int index);
  virtual int        Type(void)  { return(0x7878); }// в CArrayObj — virtual int Type(void) const { return(0x7778); }
  //---
 } tmpObj;
/********************************************************************/
bool CTestClass::CreateElement(const int index)
 {
  CTestClass *newObj=new CTestClass;
  if(!CheckPointer(newObj))
    return false;
  m_data[index] = newObj;
  return true;
 }
/********************************************************************/
void CTestClass::fileWrite(void)
 {
  int handle=FileOpen(this.m_fileName,FILE_WRITE|FILE_BIN|FILE_COMMON);
  if(handle!=INVALID_HANDLE)
   {
    //CArrayObj::Save(handle);
    list.Save(handle);
    FileClose(handle);
   }
  printf("В файл записан список из %d объектов",list.Total());
 }
/********************************************************************/
void CTestClass::fileRead(void)
 {
  if(!FileIsExist("TestHist.bin",FILE_COMMON))
    return;
  int handle=FileOpen(this.m_fileName,FILE_READ|FILE_BIN|FILE_COMMON);
  if(handle!=INVALID_HANDLE)
   {
    CArrayObj::Load(handle);
    //list.Load(handle);
    FileClose(handle);
   }
   else Print(GetLastError());
  printf("Из файла в список прочитано %d объектов",list.Total());
 }
/********************************************************************/
bool CTestClass::Load(const int file_handle)
 {
  CTestClass *newObj=new CTestClass;
  newObj.setSymbol(FileReadString(file_handle,6));
  newObj.setID(FileReadLong(file_handle));
  newObj.setType(FileReadLong(file_handle));
  newObj.setMagic(FileReadLong(file_handle));
  newObj.setTicketIn(FileReadLong(file_handle));
  newObj.setTicketOut(FileReadLong(file_handle));
  newObj.setPriceIn(FileReadDouble(file_handle));
  newObj.setPriceOut(FileReadDouble(file_handle));
  newObj.setVolume(FileReadDouble(file_handle));
  if(!list.Add(newObj))
    delete newObj;
  return true;
 }
/********************************************************************/
bool CTestClass::Save(const int file_handle)
 {
  if(!FileWriteString(file_handle,getSymbol()))
    return false;
  if(!FileWriteLong(file_handle,getID()))
    return false;
  if(!FileWriteLong(file_handle,getType()))
    return false;
  if(!FileWriteLong(file_handle,getMagic()))
    return false;
  if(!FileWriteLong(file_handle,getTicketIn()))
    return false;
  if(!FileWriteLong(file_handle,getTicketOut()))
    return false;
  if(!FileWriteDouble(file_handle,getPriceIn()))
    return false;
  if(!FileWriteDouble(file_handle,getPriceOut()))
    return false;
  if(!FileWriteDouble(file_handle,getVolume()))
    return false;
  return true;
 }
/********************************************************************/
void CTestClass::listCreate(long posID)
 {
  tmpObj.setID(posID);
  list.Sort(0);
  if(list.Search(&tmpObj) == -1)
   {
    CTestClass *obj = new CTestClass();
    HistorySelectByPosition(posID);
    int dealsTotal=HistoryDealsTotal();
    obj.setID(posID);
    for(int i=0; i<dealsTotal; i++)
     {
      ulong dealTicket = HistoryDealGetTicket(i);
      ENUM_DEAL_ENTRY entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket,DEAL_ENTRY);
      switch(entry)
       {
        case DEAL_ENTRY_IN:
          obj.setType(HistoryDealGetInteger(dealTicket,DEAL_TYPE));
          obj.setSymbol(HistoryDealGetString(dealTicket,DEAL_SYMBOL));
          obj.setTicketIn(dealTicket);
          obj.setPriceIn(HistoryDealGetDouble(dealTicket,DEAL_PRICE));
          obj.setMagic(HistoryDealGetInteger(dealTicket,DEAL_MAGIC));
          obj.setVolume(HistoryDealGetDouble(dealTicket,DEAL_VOLUME));
          break;
        case DEAL_ENTRY_OUT:
          obj.setTicketOut(dealTicket);
          obj.setPriceOut(HistoryDealGetDouble(dealTicket,DEAL_PRICE));
          break;
       }
     }
    if(!list.Add(obj))
     {
      delete obj;//--- Если добавление завершилось ошибкой — удалим этот объект
      printf("Что-то пошло не так %s %d",__FUNCTION__,__LINE__);
     }
   }
//---
  HistorySelect(startTime,TimeCurrent());
 };/*****************************************************************/

/********************************************************************/
int CTestClass::Compare(const CObject *node, const int mode = 0) const
 {
  const CTestClass *obj = node;
//--- при mode==0 сравниваем POSITION_ID
  if(mode == 0)
    return compare(this.m_posID, obj.getID());
//--- при mode==1 сравниваем символ
  if(mode == 1)
    return compare(this.m_symbol, getSymbol());
  return -1;
 };/*****************************************************************/

/********************************************************************/
template <typename T1>
int CTestClass::compare(T1 data1, T1 data2) const
 {
  if(data1 == data2)
    return 0;
  if(data1 > data2)
    return 1;
  return -1;
 };/*****************************************************************/

/********************************************************************\
\********************************************************************/
void CTestClass::CTestClass(void):
  m_symbol(NULL),
  m_fileName("TestHist.bin")
 {
 }/******************************************************************/
/********************************************************************\

И два файла советников, запись и чтение списка

/********************************************************************\
|                                                   HistoryWrite.mq5 |
|                                            © 2024, Alexey Viktorov |
|                       https://www.mql5.com/ru/users/alexeyvik/news |
\********************************************************************/
#property copyright "© 2024, Alexey Viktorov"
#property link      "https://www.mql5.com/ru/users/alexeyvik/news"
#property version   "1.00"
#include "TestSaveLoadObj.mqh"
/*******************Expert initialization function*******************/
int OnInit()
 {
  list.Clear();
  HistorySelect(startTime,TimeCurrent());
  int n=0,dealsTotal = HistoryDealsTotal();
  for(int i=0; i<dealsTotal; i++)
   {
    ulong dealTicket = HistoryDealGetTicket(i);
    long posID=HistoryDealGetInteger(dealTicket,DEAL_POSITION_ID);
    if(posID>0)
      tmpObj.listCreate(posID);
   }
  tmpObj.fileWrite();
  return(INIT_SUCCEEDED);
 }/******************************************************************/

/************************Expert tick function************************/
void OnTick()
 {
 }/******************************************************************/

/******************Expert deinitialization function******************/
void OnDeinit(const int reason)
 {
//EventKillTimer();
 }/******************************************************************/
/********************************************************************\
|                                                    HistoryRead.mq5 |
|                                            © 2024, Alexey Viktorov |
|                       https://www.mql5.com/ru/users/alexeyvik/news |
\********************************************************************/
#property copyright "© 2024, Alexey Viktorov"
#property link      "https://www.mql5.com/ru/users/alexeyvik/news"
#property version   "1.00"
#include "TestSaveLoadObj.mqh"
/*******************Expert initialization function*******************/
int OnInit()
 {
  list.Clear();
  tmpObj.fileRead();
  return(INIT_SUCCEEDED);
 }/******************************************************************/

/************************Expert tick function************************/
void OnTick()
 {
 }/******************************************************************/

/******************Expert deinitialization function******************/
void OnDeinit(const int reason)
 {
//EventKillTimer();
 }/******************************************************************/

На сколько я смог понять, функция класса CArrayObj

         //--- write array type
         if(FileWriteInteger(file_handle,Type(),INT_VALUE)==INT_VALUE)
virtual int       Type(void) const { return(0x7778); }

Записывает некое число и чтобы прочесть этот файл надо «предъявить» это число.

         //--- read and check array type
         if(FileReadInteger(file_handle,INT_VALUE)==Type())


Но если всегда использовать значение из класса CArrayObj то смысл этого теряется вообще.

Что я делаю не правильно? Почему не вызывается моя виртуальная функция, а всегда срабатывает из класса CArrayObj?

Файлы: