OOP, modelos e macros em mql5, sutilezas e usos

 
Comentários não relacionados a "características de linguagem mql5, complexidades e truques" foram movidos para este tópico.
 
fxsaber:

Este é o comportamento padrão da MQL5: as variáveis estáticas começam a funcionar após as variáveis globais.

Você pode realmente se meter em muitos problemas por causa disso.

A questão deste "comportamento padrão" ainda não foi resolvida universalmente, como lidar com ela? Até agora, a única maneira que posso ver é substituir todas as variáveis estáticas por globais (a força do MQ diretamente para globalizar tudo)). Mas isso não pode ser feito em modelos e macros.

Os campos de modelos estáticos também são inicializados após variáveis globais.

 

Em geral, o problema é resolvido. Usando uma bandeira estática adicional, verificamos se nossa variável é inicializada. Caso contrário, a procuramos na lista de valores previamente gravados. Se não estiver lá, nós o acrescentamos lá. Tudo está envolto em uma macroSTATIC_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();
}

Resultado:

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

 
fxsaber:

Este é o comportamento padrão da MQL5: as variáveis estáticas são iniciadas após as globais.

Podemos nos meter em muitos problemas por causa disso.

Na MQL, as variáveis estáticas são inicializadas na pilha global, não por local de declaração, como é em C++.

Em minha opinião não faz diferença o que inicializar primeiro, estático ou global (exceto pelo gosto) - em qualquer caso, haverá quem sofra.

Seria mais correto inicializar as variáveis estáticas e globais que são inicializadas com constantes primeiro, depois todo o resto na ordem de detecção por compilador (já está no plano, mas infelizmente não no calendário).

E esta ordem é diferente da ordem de C++.
Condicionalmente, a compilação em MQL é feita em duas passagens: primeiro são coletadas todas as definições globais e depois é feita a compilação dos corpos funcionais - é por isso que as variáveis estáticas são adicionadas ao pool após a

 
Ilyas:

(isto já está no plano, mas infelizmente não no calendário).

E me parece que esta questão é uma prioridade. Porque a situação atual viola a lógica de execução do programa, o que é simplesmente inaceitável para uma linguagem de programação. E todo tipo de truques e novas funções são de importância secundária.
 

Eu refinei o código, inclusive para matrizes estáticas. Desta vez estou anexando-o como um arquivo.

Existem 4 macros para uso no código:

1) STATIC_SET(var, data) - atribui à variável estática var os dados de valor (via operator=), ou copia os dados de array para o array estático 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) - inicializa a variável var com dados (seja via construtor ou operador=), ou inicializa a matriz var com constantes - definida dentro de parênteses curvos e opcionalmente entre parênteses normais:

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

3) STATIC(tipo, var, valor) - declaração e inicialização de uma variável estática:

STATIC(int, a, 10);

4) STATIC_ARR(tipo, var, valores) - declaração e inicialização de uma matriz estática:

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


Nos dois primeiros pontos, você precisa considerar que o tamanho declarado da matriz estática não deve ser inferior ao número de valores inicializadores, caso contrário, haverá um erro.

E você não pode inicializar matrizes dinâmicas com macros. Seu tamanho não mudará até que o inicializador regular chegue à função.

Arquivos anexados:
StaticVar.mqh  12 kb
 
E quanto às perguntas sobre por que precisamos de tudo isso, deixe-me explicar: é para garantir que nosso código funcione corretamente, exatamente como o algoritmo deve funcionar, e não como os desenvolvedores de MQL ou qualquer outra pessoa queria que funcionasse.
 
Alexey Navoykov:
Quanto às perguntas sobre por que precisamos de tudo isso, esclareço: é para fazer nosso código funcionar corretamente, exatamente como o algoritmo deveria funcionar, não como os desenvolvedores do MQL ou qualquer outra pessoa desejava que funcionasse.

Você pode nos mostrar um exemplo quando tudo isso poderia simplificar ou encurtar a escrita de códigos, ou pelo menos evitar erros. E, por favor, não com funções abstratas, mas o mais próximo possível das realidades do comércio em uma EA ou indicador.

 
Alexey Viktorov:

E você pode me mostrar um exemplo quando tudo isso poderia simplificar a escrita do código, reduzi-lo ou pelo menos protegê-lo de erros. E, por favor, não com funções abstratas, mas o mais próximo possível das realidades do comércio, em uma EA ou indicador.

Você quer dizer o mais próximo possível? Você quer que eu escreva tal código especificamente para você, ou que poste meus projetos? Eu não preciso fazê-lo.

Aqui, temos um objeto global de programa ou EA: CExpert Expert; ou CProgram Program; Ele é naturalmente inicializado de alguma forma dentro por padrão (incluindo todos os objetos internos, dos quais há muitos), talvez em algum lugar sejam usados para estas funções globais auxiliares, e estas funções podem conter variáveis estáticas.Também são utilizadas classes que possuem campos estáticos, que também se relacionam com variáveis estáticas, e portanto o trabalho dessas classes depende dos valores dos campos estáticos. Valores de campo incorretos significam objetos de classe inicializados incorretamente. Continue você mesmo com esta corrente lógica. E o pior é que só aprendemos sobre isso quando executamos o programa.

Não tenho pensado tudo do zero. Muitas vezes me deparo com indicadores quebrados que deveriam ser inicializados ou matrizes não preenchidas que deveriam ser inicializadas com valores. E estou cansado de cavar constantemente esses pequenos detalhes e modificar o código para satisfazer o algoritmo de inicialização aceito pelos desenvolvedores do MQ.

Na verdade - é um bug, e nada mais. Se os desenvolvedores adotaram uma certa seqüência de inicialização de variáveis, então o código deve ser executado de acordo com essa seqüência, em vez de contorná-la.

Se tudo isso não lhe é familiar e você não usa a funcionalidade descrita e escreve, por exemplo, no estilo de Peter Konov, então, boa viagem, esses problemas não lhe tocaram, parabéns por isso.

 
Alexey Navoykov:

Não inventei tudo isso do zero. Muitas vezes me deparo com indicadores quebrados, que deveriam ter sido inicializados, ou matrizes não preenchidas, que deveriam ter sido inicializadas por valores. E escavar constantemente estas pequenas coisas e mudar o código para se adequar ao algoritmo de inicialização, adotado pelos desenvolvedores da MQ, é irritante.

Já tive muitas situações de declaração de campos estáticos em classes que inicializam globalmente (antes do OnInit) e em caso de declaração repetida de campo estático logo após a descrição da classe e antes da declaração da variável global de sua instância - nunca tive problemas com a inicialização do campo estático (como neste caso é considerado global e inicializado antes da instância de classe, como entendo). Portanto, basta recusar a declaração de variáveis estáticas dentro de métodos e funções e não há problema.

 
Ilya Malev:

Já tive muitas situações de declarar campos estáticos em classes que são inicializadas globalmente (antes do OnInit), contanto que você re-clareça o campo estático logo após a descrição da classe, e antes de declarar variável global de sua instância, nunca houve qualquer problema com a inicialização do campo estático (porque neste caso é considerado global e inicializado antes da instância de classe, como eu entendo). Portanto, basta recusar a declaração de variáveis estáticas dentro de métodos e funções e não há problema algum.

É claro que não sou muito bom com o OOP, portanto não posso explicar claramente, mas ainda assim quero corrigir sua afirmação. Você não pode recusar completamente a declaração de variáveis estáticas dentro de métodos e funções, mas, pelo menos, não pode inicializar outras variáveis estáticas com aqueles métodos ou funções que contêm variáveis estáticas.

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

void OnTick()
{
 static int b=a(9);
}
Neste exemplo, a variável estática int b será inicializada primeiro, mas a variável estática int f dentro da função int a(int n) ainda não está inicializada e, como resultado, ficamos com algaraviadas.
Razão: