ООП, шаблоны и макросы в mql5, тонкости и приёмы использования

Авторизуйтесь или зарегистрируйтесь, чтобы добавить комментарий
Alexey Navoykov
4569
Alexey Navoykov  
fxsaber:

Это стандартное поведение MQL5: static-переменные начинают работать после глобальных.

Можно очень серьезно нарваться из-за этого.

Продолжим тему насчёт этого "стандартного поведения".  Никто не придумал универсальное решение, как с ним бороться?   Я пока вижу только замену всех статических переменных на глобальные (MQ прям вынуждают выносить всё на глобальный уровень ))   Но этого не сделать в шаблонах и в макросах.

Статические поля шаблона тоже инициализируются после глобальных переменных.

Alexey Navoykov
4569
Alexey Navoykov  

В общем, задача решена.  С помощью дополнительного статического флага проверяем, инициализирована ли наша переменная.  Если нет, то ищем её в списке ранее сохранённых значений. Если там нет, то добавляем туда.   Всё обёрнуто в макрос STATIC_SET

class CData
{
 public: 
  string name;
};

template<typename T>
class CDataT : public CData
{
 public:
  T      value;
  CDataT(string name_, T const& value_) { name= name_;  value=value_; }
};


class CDataArray
{
  CData*_data[];
 public: 
  ~CDataArray()                    { for (int i=0; i<ArraySize(_data); i++) delete _data[i]; }
  
  CData* AddPtr(CData* ptr)        { int i=ArraySize(_data); return ArrayResize(_data, i+1)>0 ? _data[i]=ptr : NULL; }
  CData* GetPtr(string name) const { for (int i=0; i<ArraySize(_data); i++) if (_data[i].name==name) return _data[i];  return NULL; }
  template<typename T>
  bool Set(string name, T const &value){ CData* ptr= new CDataT<T>(name, value);  return ptr!=NULL && AddPtr(ptr)!=NULL; }   
  template<typename T>  // Данная перегрузка необходима для передачи указателей, ибо баг в MQL
  bool Set(string name, T &value)      { CData* ptr= new CDataT<T>(name, value);  return ptr!=NULL && AddPtr(ptr)!=NULL; }   
  template<typename T>
  bool Get(string name, T &value) const
                                   { CDataT<T>* ptr= dynamic_cast<CDataT<T>*> ( GetPtr(name) );
                                     if (ptr) value= ptr.value;
                                     return ptr!=NULL;
                                   }
 private: 
  template<typename T>
  bool Get(string name, T const&value) const; // =delete;
}; 

CDataArray __GlobData;


#define __STATIC_SET(var, name, value) \
{ \
  static bool inited=false; \
  if (!inited && !__GlobData.Get(name, var)) { \  // Если копия переменной ещё не сохранена, то сохраняем её
    var=value;  if (!__GlobData.Set(name, var)) MessageBox("Failed to set static var:  "+name, "Error", 0); \
  }\  
  inited=true; \
}

#define STATIC_SET(var, value) __STATIC_SET(var, __FUNCSIG__+"::"+#var, value)



//--- Проверка ---


class __CPrint { public: __CPrint(string str) { Print(str); } } __Print("=======");


uint StaticTickCount()
{
  static uint tickcount = GetTickCount();
  Print("mql static value: ",tickcount);

  STATIC_SET(tickcount, GetTickCount());
  Print("my static value: ",tickcount);
  Sleep(50);
  return tickcount;
}

uint TickCount= StaticTickCount();

void OnStart()
{  
  Print("OnStart");
  StaticTickCount();
}

Результат:

mql static value: 0
my static value: 940354171
OnStart
mql static value: 940354218
my static value: 940354171

Ilyas
1442
Ilyas  
fxsaber:

Это стандартное поведение MQL5: static-переменные начинают работать после глобальных.

Можно очень серьезно нарваться из-за этого.

static переменные в MQL инициализируются в глобальном скопе, а не по месту объявления как это в C++

По моему мнению, разницы, что инициализировать первым, static или глобальные, нет (кроме как на вкус) - в любом случае найдутся пострадавшие.

Правильнее будет сначала инициализировать static и глобальные переменные, которые инициализируются константами, затем все остальные в порядке обнаружения компилятором (это уже имеется в плане, но к сожалению не в календарном).

А порядок этот отличается от порядка C++.
Условно, компиляция в MQL проходит в два прохода: сначала собираются все глобальные определения, затем происходит компиляция тел функций - именно поэтому static переменные добавляются в пул после глобальных

Alexey Navoykov
4569
Alexey Navoykov  
Ilyas:

(это уже имеется в плане, но к сожалению не в календарном).

А мне кажется, это вопрос приоритетный. Потому как в текущей ситуации нарушается логика выполнения программы, что просто неприемлемо для языка программирования.  А всякие навороты и новые функции - это уже второстепенно.
Alexey Navoykov
4569
Alexey Navoykov  

Я доработал код, в том числе для статических массивов.  На этот раз прикрепляю в виде файла.

Для использования в кодах предусмотрено 4 макроса:

1)  STATIC_SET(var, data)    - присваивает статической переменной var значение data (через оператор=), либо копирует массив data в статический массив var

static int a;
STATIC_SET(a, 10);

static int arr[5];
const int data[]= { 1, 2, 3, 4, 5 };
STATIC_SET(arr, data);

 2)  STATIC_INIT(var, data)   - инициализирует переменную var значением data (через конструктор или оператор=), либо инициализирует массив var константами - задаются в фигурных скобках и дополнительно обрамляются обычными скобками:

static int arr[5];
STATIC_INIT(arr, ({1, 2, 3, 4, 5}));

3)  STATIC(type, var, value) - объявление и инициализация статической переменной:

STATIC(int, a, 10);

4) STATIC_ARR(type, var, values) - объявление и инициализация статического массива:

STATIC_ARR(int, arr, ({1, 2, 3, 4, 5}));


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

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

Файлы:
StaticVar.mqh 12 kb
Alexey Navoykov
4569
Alexey Navoykov  
И по поводу вопросов, зачем это всё надо, поясняю:  это надо для того, что наш код работал правильно, именно так, как задумано в алгоритме,  а не так, как задумали разработчики MQL или кто-либо ещё.
Alexey Viktorov
25696
Alexey Viktorov  
Alexey Navoykov:
И по поводу вопросов, зачем это всё надо, поясняю:  это надо для того, что наш код работал правильно, именно так, как задумано в алгоритме,  а не так, как задумали разработчики MQL или кто-либо ещё.

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

Alexey Navoykov
4569
Alexey Navoykov  
Alexey Viktorov:

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

В смысле максимально приближёнными? Вы хотите чтобы я специально для вас написал такой код, или выкладывал свои проекты?  Мне это незачем.

Я просто объясню на пальцах.  Вот у нас есть глобальный объект программы или советника:  CExpert Expert;  или CProgram Program;    Он естественно как-то внутри инциализируется по умолчанию (включая все внутренние объекты, коих немало), возможно где-то используются для этого вспомогательные глобальные функции, а в этих функциях возможно присутствуют статические переменные.  А также в работе используются классы, у которых есть статические поля - они тоже относятся к статическим переменным,  и соответственно работа этих классов завязана на значениях статических полей. Неверные значения полей - неверно инициализированный объект класса.  Дальше логическую цепочку сами продолжите.  И самое плохое, что мы об этом узнаём только в процессе выполнения программы.

Я же не с пустого места всё это придумывал.  Часто приходится натыкаться на битые указатели, которые должны были быть инициализированы, или незаполненные массивы, которые должны были быть инциализированы значениями.  А постоянно ковыряться в этих мелочах и переделывать код в угоду алгоритму инициализации, принятому разработчиками MQ - поднадоело порядком.

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

Если для вас это всё незнакомо, вы не пользуетесь описанным функционалом, а пишете например в стиле Петра Конова, то флаг вам в руки, эти проблемы вас не коснулись, с чем и поздравляю.

Ilya Malev
12328
Ilya Malev  
Alexey Navoykov:

Я же не с пустого места всё это придумывал.  Часто приходится натыкаться на битые указатели, которые должны были быть инициализированы, или незаполненные массивы, которые должны были быть инциализированы значениями.  А постоянно ковыряться в этих мелочах и переделывать код в угоду алгоритму инициализации, принятому разработчиками MQ - поднадоело порядком.

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

Alexey Viktorov
25696
Alexey Viktorov  
Ilya Malev:

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

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

int a(int n)
{
 static int f=7;
 return(f+=n);
}

void OnTick()
{
 static int b=a(9);
}
В этом примере сначала будет инициализирована переменная static int b но пока ещё не инициализирована переменная static int f находящаяся внутри функции int a(int n) и в результате получим белиберду.