Распространенная ситуация при программинге, как быть? Быстродействие vs Лаконичность - страница 3

 

Ну не дает MQL писать лаконично и красиво иногда:

#property strict

#define AMOUNT 100

class A
{
private:
  int Array[AMOUNT];

public:
  const int operator []( int Pos ) const
  {
    return(Array[Pos]);
  }
};

class B
{
private:
  int Array[AMOUNT];

  template<typename T>
  void Func( const T &a )
  {
    for (int i = 0; i < AMOUNT; i++)
      Array[i] = a[i];
    
    return;
  }
  
// На кой черт в MQL при передаче ссылки на массив нужно указывать эти хреновы скобки: []?
// Очередной костыль при переходе c С++, заставляющий перегружать функции лишь добавкой [].  
  template<typename T>
  void Func( const T &a[] ) // все отличие - добавлены []
  {
    for (int i = 0; i < AMOUNT; i++)
      Array[i] = a[AMOUNT - 1 - i];
    
    return;
  }
  
public:
  B( const A &a )
  {
    Func(a);
    Func(Array);
  }
};

void OnStart( void )
{
  A a;
  B b(a);
  
  return;
}
Разработчики, зачем такое ограничение?
 
lob32371:

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

Ну ты решил разобрать напрямую код, который был показан только для демонстрации проблемы. GetNums может выглядеть гораздо силнее, и StructN могут быть ничем не похожими. В общем случае FlagSource -целая, в GetNums вместо if стоит либо switch, либо сложные условные выражения. Короче, пример показывает концепцию, а не конкретную ситуацию.

Значит, код, всё-таки, достаточно разный...

И есть дискомфорт от проверки ненужных условий, а также от повторяющихся кусков кода, но нет дискомфорта от макросов, которые как раз и создаются для повторения...

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

/******************************************************************************/
void Calculate( const STRUCT1 &Source[] ) // Версия Calculate для STRUCT1
{
// здесь определяется много переменных для дальнейших вычислений
// ....

  for (int i = 0; i < AMOUNT; i++)
  { 
    int Num1 = Source[i].bid;
    int Num2 = Source[i].ask;

// здесь большой кусок вычислительного кода, использующий Num1 и Num2 и ранее заданные переменные
// поэтому вынести его в отдельную функцию (все равно проинлайнится при компилировании) крайне проблематично
// - понадобится прописывать очень много входных параметров.
// ....
  }
}

/******************************************************************************/
void Calculate( const STRUCT2 &Source[] ) // Версия Calculate для STRUCT2
{
// здесь определяется много переменных для дальнейших вычислений
// ....

  for (int i = 0; i < AMOUNT; i++)
  { 
    int Num1 = Source[i].min;
    int Num2 = Source[i].max;

// здесь большой кусок вычислительного кода, использующий Num1 и Num2 и ранее заданные переменные
// поэтому вынести его в отдельную функцию (все равно проинлайнится при компилировании) крайне проблематично
// - понадобится прописывать очень много входных параметров.
// ....
  }
}

/******************************************************************************/
void Calculate( const bool FlagSource ) // Общая версия Calculate
{ 
  if (FlagSource) {
    Calculate(Source1);
  } else {
    Calculate(Source2);
  }
}
Не знаю, насколько такое решение годится для реального кода.
 
simpleton:

Значит, код, всё-таки, достаточно разный...

И есть дискомфорт от проверки ненужных условий, а также от повторяющихся кусков кода, но нет дискомфорта от макросов, которые как раз и создаются для повторения...

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

Не знаю, насколько такое решение годится для реального кода.

Нет, ну макросы позволяют копи-пастить не самому, а компилятору. Поэтому код, как миниму, лаконичнее выглядит. Хотя красивым его не назвать, конечно.

 

Постом выше хотел перегрузку записать через шаблоны (чтобы не копи-пастить) - обломался.

 

ЗЫ Прокомменируй. 

 

Написал общий интерфейс, но что-то снова не клеется. Желаемое видно из этого кода:

#property strict

class INTERFACE
{
protected:
  int Array[];

public:
  template <typename T>
  virtual T operator []( int Pos ) // 'operator[]' - template function cannot be virtual
  {
//  return(Array[Pos]);
  }
  
};

struct STRUCT1
{
  int a;
};

struct STRUCT2
{
  int a, b;
};

class A1 : public INTERFACE
{
  public:
    virtual STRUCT1 operator []( int Pos ) // 'operator[]' - overriding virtual function with different return type
    {
      STRUCT1 Res;
      // ...
      
      return(Res);
    }
};

class A2 : public INTERFACE
{
  public:
    virtual STRUCT2 operator []( int Pos ) // 'operator[]' - overriding virtual function with different return type
    {
      STRUCT2 Res;
      // ...
      
      return(Res);
    }
};

void OnStart( void )
{
  A1 a1;
  A2 a2;
  
  return;
}
Подскажите, как прописать общий интерфейс так, чтобы оператор [] возвращал различный тип данных, в зависимости от производного класса?
 
lob32371:

Ну не дает MQL писать лаконично и красиво иногда:

Разработчики, зачем такое ограничение?

Кстати, данная декларация:

  template<typename T>
  void Func( const T &a[] ); // все отличие - добавлены []

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

А в MQL4++ - компилируется и означает ссылку на массив. Команда разработчиков явно не тянет ни дизайн языка подобной навороченности, ни его работающую реализацию.

Вот, взять, хотя бы ту же самую перегрузку. Она - есть, но её не используют. Например, есть такая функция MarketInfo(), объявленная так:

double MarketInfo(const string symbol, ENUM_MARKETINFO type);

Но для некоторых констант ENUM_MARKETINFO возвращаемое значение - совсем не типа double, а типа int или даже datetime. Поэтому при компиляции данного кода появляются предупреждения:

#property strict

/* Условный гипотетический заголовочный файл мог бы содержать следующее определение типа ENUM_MARKETINFO
enum ENUM_MARKETINFO {
  MODE_DIGITS, ...
  MODE_STARTING, ...
  MODE_POINT, ...
};
*/

// Условный гипотетический заголовочный файл мог бы содержать следующее определение функции MarketInfo()
double MarketInfo(const string symbol, ENUM_MARKETINFO type);

// Теперь код:
void OnStart( void ) {
  int digits = MarketInfo(Symbol(), MODE_DIGITS); // possible loss of data due to type conversion
  datetime dt = MarketInfo(Symbol(), MODE_STARTING); // possible loss of data due to type conversion
  double point = MarketInfo(Symbol(), MODE_POINT);
}

Почему не была применена перегрузка? Например, прототипы в гипотетическом заголовочном файле могли бы выглядеть вот так:

/******************************************************************************/
enum ENUM_MARKETINFO_INT {
  MODE_DIGITS, ...
};

/******************************************************************************/
enum ENUM_MARKETINFO_DATETIME {
  MODE_STARTING, ...
};

/******************************************************************************/
enum ENUM_MARKETINFO_DOUBLE {
  MODE_POINT, ...
};

/******************************************************************************/
int MarketInfo(const string symbol, ENUM_MARKETINFO_INT type);
datetime MarketInfo(const string symbol, ENUM_MARKETINFO_DATETIME type);
double MarketInfo(const string symbol, ENUM_MARKETINFO_DOUBLE type);

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

Вот код, эмулирующий наличие подобной реализации и демонстрирующий, что перегрузка в данном месте прекрасно справляется с поставленной задачей:

#property strict

enum ENUM_MARKETINFO_INT {
  MODE_DIGITS_ = MODE_DIGITS, /* ... */
};

enum ENUM_MARKETINFO_DATETIME {
  MODE_STARTING_ = MODE_STARTING, /* ... */
};

enum ENUM_MARKETINFO_DOUBLE {
  MODE_POINT_ = MODE_POINT, /* ... */
};

int MarketInfo(const string symbol, ENUM_MARKETINFO_INT type);
datetime MarketInfo(const string symbol, ENUM_MARKETINFO_DATETIME type);
double MarketInfo(const string symbol, ENUM_MARKETINFO_DOUBLE type);

/******************************************************************************/
int MarketInfo(const string symbol, ENUM_MARKETINFO_INT type) {
  Print("int MarketInfo(const string symbol, ENUM_MARKETINFO_INT type)");
  return (int)MarketInfo(symbol, (ENUM_MARKETINFO)type);
}

/******************************************************************************/
datetime MarketInfo(const string symbol, ENUM_MARKETINFO_DATETIME type) {
  Print("datetime MarketInfo(const string symbol, ENUM_MARKETINFO_DATETIME type)");
  return (datetime)MarketInfo(symbol, (ENUM_MARKETINFO)type);
}

/******************************************************************************/
double MarketInfo(const string symbol, ENUM_MARKETINFO_DOUBLE type) {
  Print("double MarketInfo(const string symbol, ENUM_MARKETINFO_DOUBLE type)");
  return MarketInfo(symbol, (ENUM_MARKETINFO)type);
}

/******************************************************************************/
void OnStart( void ) {
  int digits = MarketInfo(Symbol(), MODE_DIGITS_);
  datetime dt = MarketInfo(Symbol(), MODE_STARTING_);
  double point = MarketInfo(Symbol(), MODE_POINT_);
}

Ни единого предупреждения, при том, что в коде OnStart() - ни единого приведения типа, потому что не требуется. Всё, что выше функции OnStart(), должно быть "где-то там", как сейчас "где-то там" находятся определения MarketInfo() и перечисления ENUM_MARKETINFO. Данный код выводит в лог следующее:

Script 3 EURUSDm,M30: loaded successfully
3 EURUSDm,M30: initialized
3 EURUSDm,M30: int MarketInfo(const string symbol, ENUM_MARKETINFO_INT type)
3 EURUSDm,M30: datetime MarketInfo(const string symbol, ENUM_MARKETINFO_DATETIME type)
3 EURUSDm,M30: double MarketInfo(const string symbol, ENUM_MARKETINFO_DOUBLE type)
3 EURUSDm,M30: uninit reason 0
Script 3 EURUSDm,M30: removed

Однако, уже 745-й build, а данная техника до сих пор не применяется, и люди продолжают спрашивать, что им делать с предупреждениями для возвращаемых значений, отличных от типа double.

Это говорит об уровне команды. Наверное, не стоит с них требовать слишком многого. Тем более, что уже никуда не деться.

 
lob32371:

Написал общий интерфейс, но что-то снова не клеется. Желаемое видно из этого кода:

Подскажите, как прописать общий интерфейс так, чтобы оператор [] возвращал различный тип данных, в зависимости от производного класса?
Например, убрать виртуальность у operator [] в базовом классе. Шаблоны методов не могут быть виртуальными даже в C++.
 
simpleton:
Например, убрать виртуальность у operator [] в базовом классе. Шаблоны методов не могут быть виртуальными даже в C++.

Спасибо, убрал. Меня удивило, что даже при разных сигнатурах оператора [], получается реализовать сокрытие, если operator [] в производном классе сделать virtual:

#property strict

#define AMOUNT 10

struct DOUBLE
{
  double a;
};

class INTERFACE
{
protected:
  double Array[AMOUNT];
//  DOUBLE Array[AMOUNT]; // чтобы компилировалось, вместо строчки выше написать эту

public:
  const double operator []( int Pos ) const
//  const DOUBLE operator []( int Pos ) const // чтобы компилировалось, вместо строчки выше написать эту
  {
    Print(__FUNCTION__);
    
    return(Array[Pos]);
  }
  
};

struct STRUCT1
{
  int a;
};

struct STRUCT2
{
  int a, b;
};

class A1 : public INTERFACE
{
  public:
    virtual const STRUCT1 operator []( int Pos ) const // без virtual: 'operator[]' - overriding virtual function with different return type
    {
      STRUCT1 Res;
      // ...
      
      return(Res);
    }
};

class A2 : public INTERFACE
{
  public:
    virtual const STRUCT2 operator []( int Pos ) const // без virtual: 'operator[]' - overriding virtual function with different return type
    {
      STRUCT2 Res;
      // ...
      
      return(Res);
    }
};

template <typename T>
void PrintType( const T &Tmp )
{
  Print(typename T);
  
  return;
}

void OnStart( void )
{
  A1 a1;
  A2 a2;
  
  INTERFACE* I = GetPointer(a1);
  
  PrintType(a1[0]);
  PrintType(a2[0]);

  PrintType(I[0]); // не компилируется (если double, а не DOUBLE): 'operator[]' - parameter passed as reference, variable expected
  
  I[0]; // А это работает всегда
  
  return;
}

Совсем неочевидное сокрытие - нигде о таком не слышал. Теперь работает, как надо.

ЗЫ Это баг?:

PrintType(I[0]); // не компилируется (если double, а не DOUBLE): 'operator[]' - parameter passed as reference, variable expected
 
lob32371:

Спасибо, убрал. Меня удивило, что даже при разных сигнатурах оператора [], получается реализовать сокрытие, если operator [] в производном классе сделать virtual:

Совсем неочевидное сокрытие - нигде о таком не слышал. Теперь работает, как надо.

ЗЫ Это баг?:

 

Странный пример. Зачем A1::operator[]() и  A2::operator[]()  сделаны виртуальными, если эта виртуальность ни где не используется?

 
Ko1dun:

Странный пример. Зачем A1::operator[]() и  A2::operator[]()  сделаны виртуальными, если эта виртуальность ни где не используется?

Так написано же в исходнике:

virtual const STRUCT1 operator []( int Pos ) const // без virtual: 'operator[]' - overriding virtual function with different return type

Никак по-другому. Изврашенцем быть ты не должен, но изврат понимать обязан!

Походу код пишу и сталкиваюсь с такими затыками. Эту странность, в частности, использую уже вовсю на практике.

Возможно, не ведаю, что творю... 

 
lob32371:

Так написано же в исходнике:

Никак по-другому. Изврашенцем быть ты не должен, но изврат понимать обязан!

Походу код пишу и сталкиваюсь с такими затыками. Эту странность, в частности, использую уже вовсю на практике.

Возможно, не ведаю, что творю... 

Это было до того как Вы убрали 'virtual' у INTERFACE::operator[]().