Учёба. Классы. Нужна помощь. - страница 21

 
Georgiy Merts #:

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

Вот мне и хочется один раз пересчитать статические данные класса, которыми пользуются все экземпляры, а не считать в каждом экземпляре. Но для каждого производного класса эта функция своя.

 
JRandomTrader #:

У меня есть базовый класс робота, ...

Да, для объединения в одном советнике многих экземпляров торговых роботов или стратегий использование такого подхода - самое то. Без виртуальных функций сделать такое можно только гораздо сложнее и некрасивее.

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

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

А вообще лучше вынести все индикаторы в отдельный класс, который будет следить за созданием и пополнением буферов всех используемых в советнике индикаторов. Планирую сделать это в будущем. Пока сделал подобную вещь для котировок по разным символам. Есть статический объект, к которому все роботы обращаются за ценами. Если это первое обращение для данного символа, то для него создаётся и добавляется в массив объект CSymbolInfo, ему обновляются котировки и этот объект отдаётся роботу. Если объект для такого символа уже был создан ранее, то он берется из массива и возвращается роботу по запросу. В начале тика все объекты CSymbolInfo из массива обновляют котировки для своих символов. А каждый из сотен роботов теперь не вызывает изнутри RefreshRates(), так как знает, что у запрошенного объекта CSymbolInfo цены и так свежие. Конечно, у такого подхода есть свои ограничения, но если они оказываются некритичными, то им вполне можно пользоваться.

 
Vladislav Boyko #:

А где проверка результата преобразования😄? Хотя, в рантайме терминал и так проверит указатель перед обращением к нему. Если из кода проверять указатель, то нужно что-то делать если передали не то, что ожидали. А из Compare "что-то делать" не сильно удобно и не сильно хочется. Даже эксепшин бросать мне не очень хотелось бы.

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

//---
#include <Object.mqh>
//+------------------------------------------------------------------+
//| CSignalData class                                                |
//+------------------------------------------------------------------+
class CSignalData : public CObject
   {
      //--- === Data members === ---
   private:
      datetime       m_dtime;  // datetime
      string         m_symbol; // symbol
      double         m_profit; // profit
      //--- === Methods === ---
   public:
      // constructor/destructor
      CSignalData(datetime _dtime, string _symbol, double _profit);
      ~CSignalData(void) {};
      //---
      datetime       GetDtime(void) const
         {
         return m_dtime;
         };
      string         GetSymbol(void) const
         {
         return m_symbol;
         };
      double         GetProfit(void) const
         {
         return m_profit;
         };
      //--- method of comparing the objects
      virtual int       Compare(const CObject *_ptr_other_sig, const int mode = 0) const;
   };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSignalData::CSignalData(datetime _dtime, string _symbol, double _profit) :
   m_dtime(_dtime), m_symbol(_symbol), m_profit(_profit)
   {
   }
//+------------------------------------------------------------------+
//| Method of comparing the objects                                  |
//+------------------------------------------------------------------+
int CSignalData::Compare(const CObject *_ptr_other_sig, const int mode = 0) const
   {
   int res = 0;
//---
   if(mode == 0)
      {
      const CSignalData* ptr_other_signal = dynamic_cast<const CSignalData*>(_ptr_other_sig);
      if(::CheckPointer(ptr_other_signal) != POINTER_INVALID)
         {
         datetime curr_sig_dtime, other_sig_dtime;
         curr_sig_dtime = GetDtime();
         other_sig_dtime = ptr_other_signal.GetDtime();
         if(curr_sig_dtime > other_sig_dtime)
            res = 1;
         else if(curr_sig_dtime < other_sig_dtime)
            res = -1;
         }
      }
//---
   return res;
   }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
   {
   CSignalData *ptr1 = new CSignalData(D'13.12.2024', _Symbol, 22);
   CSignalData *ptr2 = new CSignalData(D'10.12.2024', _Symbol, 10);
   int compare_res = ptr1.Compare(ptr2);
   ::PrintFormat("Compare result: %d", compare_res);
   //---
   delete ptr1;
   delete ptr2;
   }
//+------------------------------------------------------------------+
 

А эксепшн наверное можно сделать как-то так:

int res = -2;

Если сравнение не произойдёт, то метод вернёт "-2".

 
Возможно (но не точно), с помощью шаблонного полиморфизма можно сделать универсальные методы сравнения и сортировки. И все классы мягкого и теплого пускай их вызывают, передавая туда необходимые поля. Звучит более привлекательно, чем преобразование и проверка указателей, но я не знаю, на сколько возможно это реализовать, я не очень дружу с шаблонами.
 
Denis Kirichenko #:

Ещё пример использования виртуальных методов.

Упоминался как-то выше метод CObject::Compare(). Вот его полное определение:


Допустим, есть потомок класса CObject, в котором хранится статистика о торговом сигнале. 



Допустим, что метод CSignalData::Compare() должен сравнивать 2 объекта сигналов по времени (m_dtime) - текущий и другой (задаётся как параметр метода - _ptr_other_sig). И тут есть нюанс. Сравнивать мы можем только объекты типа CSignalData. Но с другой стороны, нам нужно сохранить сигнатуру метода: тип параметра _ptr_other_sig должен оставаться как CObject*.

Как быть? ))

Это экзамен?

Как я понял из написанного Артёмом Тришкиным, метод Compare() вызывается из 

//+------------------------------------------------------------------+
//| Search of position of element in a sorted array                  |
//+------------------------------------------------------------------+
int CArrayObj::Search(const CObject *element) const
  {
   int pos;
//--- check
   if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1)
      return(-1);
//--- search
   pos=QuickSearch(element);
   if(m_data[pos].Compare(element,m_sort_mode)==0)
      return(pos);
//--- not found
   return(-1);
  }

Следовательно сравнение так влоб или нецелесообразно делать или просто неправильно.

Там что писал Артём последовательность такая:

Создаётся временный объект.

Ищется в списке и если не находится, добавляется в список.

Вот тут как раз вызывается  Compare() в котором мы можем написать один, два или более критериев сравнения. То-есть можно поставить только m_dtime или m_dtime и m_symdol

 
Denis Kirichenko #:

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

А почему бы не сделать так:

//+------------------------------------------------------------------+
//| Method of comparing the objects                                  |
//+------------------------------------------------------------------+
int CSignalData::Compare(const CObject *_ptr_other_sig, const int mode = 0) const
   {
   int res = 0;
//---
   if(mode == 0)
      {
         datetime curr_sig_dtime, other_sig_dtime;
         curr_sig_dtime = GetDtime();
         other_sig_dtime = ((CSignalData*) ptr_other_signal).GetDtime();
         if(curr_sig_dtime > other_sig_dtime)
            res = 1;
         else if(curr_sig_dtime < other_sig_dtime)
            res = -1;
       }
      
//---
   return res;
   }

Метод Compare с высокой вероятностью не будет вызываться в неизвестных обстоятельствах, его будут вызывать только методы сортировки и поиска. А там в сортируемом массиве или массиве для поиска лежат только указатели на одинаковый класс. Поэтому указатель в *_ptr_other_sig должен всегда успешно преобразовываться из CObject* в CSignalData*.

 
Yuriy Bykov #:
А почему бы не сделать так:

Наверное потому, что с dynamic_cast не упадет, а без него упадет (опять-же, не точно, я не проверял)

https://www.mql5.com/ru/docs/basis/types/casting#dynamic_cast

class CBar { };
class CFoo : public CBar { };
 
void OnStart()
  {
   CBar bar;    
//--- динамическое приведение типа указателя *bar к указателю *foo разрешено 
   CFoo *foo = dynamic_cast<CFoo *>(&bar); // критической ошибки выполнения не возникнет   
   Print(foo);                             // foo=NULL      
//--- попытка явного приведения ссылки объекта типа Bar к объекту типа Foo запрещено
   foo=(CFoo *)&bar;                       // возникнет критическая ошибка выполнения
   Print(foo);                             // эта строка не будет выполнена
  }
 

Я выше прилагал свою реализацию метода Compare:

int CFrameData::Compare(const CObject *poNode,const int iMode) const
{
        // Преобразуем указатель на реальный объект. 
        // При этом - проходят проверки, исключающие возможность сравнения не с объектом  CFrameData (или его наследником)              
         CFrameData* pfdAnother = CONVERT_CONST_OBJECT_WITH_CHECK(poNode,CFrameData,MOT_FRAME_DATA);
   
        // Теперь, в зависимости от режима сортировки - вызываем одну из функций, которая сравнивает текущий объект с предоставленным (уже преобразованным к CFrameData):
        switch(iMode)
                {
              case FCS_BY_PASS_A:              return(_CompareByPassWith(pfdAnother,true));
              case FCS_BY_PASS_D:              return(_CompareByPassWith(pfdAnother,false));
              case FCS_BY_NAME_A:              return(_CompareByNameWith(pfdAnother,true));
              case FCS_BY_NAME_D:              return(_CompareByNameWith(pfdAnother,false));
              case FCS_BY_ID_A:                return(_CompareByIDWith(pfdAnother,true));
              case FCS_BY_ID_D:                return(_CompareByIDWith(pfdAnother,false));
              case FCS_BY_VALUE_A:             return(_CompareByValueWith(pfdAnother,true));
              case FCS_BY_VALUE_D:             return(_CompareByValueWith(pfdAnother,false));
              case FCS_BY_GRAILRATIO_A:        return(_CompareByGrailWith(pfdAnother,true));
              case FCS_BY_GRAILRATIO_D:        return(_CompareByGrailWith(pfdAnother,false));
              case FCS_BY_INPUTS_A:            return(_CompareByInputsWith(pfdAnother,true));
              case FCS_BY_INPUTS_D:            return(_CompareByInputsWith(pfdAnother,false));
              case FCS_BY_ONTESTER_A:          return(_CompareByOntesterWith(pfdAnother,true));
              case FCS_BY_ONTESTER_D:          return(_CompareByOntesterWith(pfdAnother,false));
              case FCS_BY_PAIRMIN_ONTESTER_A:  return(_CompareByPairminOntesterWith(pfdAnother,true));
              case FCS_BY_PAIRMIN_ONTESTER_D:  return(_CompareByPairminOntesterWith(pfdAnother,false));
              case FCS_BY_SL_A:                return(_CompareBySLWith(pfdAnother,true));
              case FCS_BY_SL_D:                return(_CompareBySLWith(pfdAnother,false));
              case FCS_BY_TP_A:                return(_CompareByTPWith(pfdAnother,true));
              case FCS_BY_TP_D:                return(_CompareByTPWith(pfdAnother,false));
      
              default:
                 break;
              };

        
        // Если мы оказались здесь - то что-то не так. 
        // Вызываем аварийное сообщение. 
        ASSERT(false);  
        return(NULL);      
};

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

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

 
Alexey Viktorov #:

Это экзамен?

Как я понял из написанного Артёмом Тришкиным, метод Compare() вызывается из 

Следовательно сравнение так влоб или нецелесообразно делать или просто неправильно.

Там что писал Артём последовательность такая:

Создаётся временный объект.

Ищется в списке и если не находится, добавляется в список.

Вот тут как раз вызывается  Compare() в котором мы можем написать один, два или более критериев сравнения. То-есть можно поставить только m_dtime или m_dtime и m_symdol

Ну почему же сразу экзамен. Насколько понимаю, тут всё на добровольных началах: Вы копаете - мы закапываем, или мы копаем - Вы закапываете ))

Да, метод сравнения вызывается как правило из других. Но в целях упрощения примера я вызвал его напрямую. И тут неправильного ничего нет. Нужно отметить, что чаще всего метод используется в сортировках. Свои сортировки тоже можно писать...