Как переопределить Compare() в CObject, чтобы CList sort() работал?

 

Я не могу найти документацию о том, как реализовать сортировку списков в mql5. Я вижу, что CList вызывает Compare() из указателя CObject. Как же я могу вызвать переопределенный метод Compare() дочернего класса из указателя родительского?

Пример:

#include <Arrays\List.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

class PriceScore : public CObject
{
protected:
   int price;
   int score;
public:
                  PriceScore(void){}
                  PriceScore(int p, int s):price(p),score(s){}
                  ~PriceScore(void){}
   int            Compare(const CObject *node,const int mode=0);
   void           Price(const int p){price = p;}
   int            Price() const {return price;}
   void           Score(const int s){score = s;}
   int            Score() const {return score;}
  
};
int PriceScore::Compare(const CObject *node,const int mode=0) //Can't call this override from CList
{
   PriceScore *pc = (PriceScore*)node;
   Print(__FUNCTION__,":Compare called. Incoming: ",pc.Score()," This: ", score); //Doesn't log because this isn't called from CObject'
   if(pc.Score()< score)return 1;
   else if(pc.Score()> score) return -1;
   else return 0;
}

void OnStart()
  {
//---
   CList list;

   list.Add( new PriceScore(100,500));
   list.Add( new PriceScore(1,5));
   list.Add( new PriceScore(13,5000));
   list.Add( new PriceScore(987987,567));
   list.Add( new PriceScore(98798778,1));
  
   PriceScore *node = NULL;
   Print("-------------------",TimeCurrent(),"--------------------");
   for(int i=0;i<list.Total();i++)
   {
      node = list.GetNodeAtIndex(i);
      Print("Price = ",node.Price(),", Score = ",node.Score());
      
   }
   list.Sort(1); //Can't call overriden child method'
  
  
   Print("-------------------SORTED--------------------");
   for(int i=0;i<list.Total();i++)
   {
      node = list.GetNodeAtIndex(i);
      Print("Price = ",node.Price(),", Score = ",node.Score());
      
   }
  
}
 

Я разобрался с этим, но оставляю решение здесь на случай, если кто-то еще столкнется с такой же проблемой.

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

int            Compare(const CObject *node,const int mode=0);

int            Compare(const CObject *node,const int mode=0) const;
 
nicholishen:

Я разобрался с этим, но оставляю решение здесь на случай, если кто-то еще столкнется с такой же проблемой.

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

int            Compare(const CObject *node,const int mode=0);

int            Compare(const CObject *node,const int mode=0) const;

Для этого нужно использовать ключевое слово 'override' всегда, когда вы переопределяете методы, таким образом компилятор кричит, если сигнатура метода изменена:

int            Compare(const CObject *node,const int mode=0) override const;

он не будет компилироваться из-за разницы в 'const'.

 

И вы также забыли ключевое слово 'virtual' в обоих случаях:

virtual int            Compare(const CObject *node,const int mode=0) override const;
 
Amir Yacoby:

И вы также забыли ключевое слово 'virtual' в обоих случаях:

virtual int            Compare(const CObject *node,const int mode=0) override const;
Нет... Я не хочу, чтобы дочерняя функция была переопределена любой возможной производной. Я пропустил const, чтобы заставить его работать, а переопределение подтвердил компилятор.
 
nicholishen:
Нет... Я не хочу, чтобы дочерняя функция была переопределена любой возможной производной. Я пропустил const, чтобы заставить его работать, а переопределение подтвердил компилятор.
Да, но по крайней мере в CObject вам нужно ключевое слово virtual.
 
Amir Yacoby:
Да, но по крайней мере в CObject вам нужно виртуальное ключевое слово.
Я понял... Я не связываюсь с базовыми классами в библиотеке, и он имеет его по умолчанию, но вы правы. Спасибо за подсказку по переопределению!
 
nicholishen: Нет... Я не хочу, чтобы дочерний класс был переопределен любым возможным производным классом.
  1. Не добавлять virtual - плохая практика, но не обязательная (за исключением CObject).
  2. Не добавление virtual ничего не меняет, он все равно может быть переопределен в производном классе.
  3. В MT4/5 нет ключевого слова final.
 
whroeder1:
  1. Не добавлять virtual - плохая практика, но не обязательная (за исключением CObject).
  2. Не добавление virtual ничего не меняет, он все равно может быть переопределен в производном классе.
Вы ошибаетесь, whroeder1.
Не добавление virtual в базу приведет к потере полиморфизма - метод будет вызываться статически, а не динамически во время выполнения.

class a
{
public:
   void Sub()
     {
      Print("a.sub");
     }
};
class b : public a
{
public:
   void Sub()
     {
      Print("b.sub");
     }
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   clsa *a;
   clsa=new b;
   clsa.Sub();
  }
Если Sub в классе a не имеет virtual, то любой указатель типа a, имеющий фактическую ссылку b, никогда не вызовет b.Sub() во время выполнения.
 
Amir Yacoby:
Вы ошибаетесь, whroeder1.
Если не добавить virtual в базу, то вы потеряете полиморфизм - метод будет вызываться статически, а не динамически во время выполнения.

class a
{
public:
   void Sub()
     {
      Print("a.sub");
     }
};
class b : public a
{
public:
   void Sub()
     {
      Print("b.sub");
     }
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   a *clsa;
   clsa=new b;
   clsa.Sub();
  }
Если Sub в классе a не имеет virtual, то любой указатель типа a, имеющий актуальную ссылку b, никогда не вызовет b.Sub() во время выполнения.
Правильно. Также отсутствие virtual означает, что производный класс может переопределять, но не будет вызываться из родительского указателя.
 
nicholishen:
Правильно. Также опущение virtual означает, что производный класс может переопределять, но не будет вызываться из родительского указателя.
Это именно тот пример, который я привел (: a - родитель, и он вызывает a.sub, а не b.sub.
Причина обращения: