OOP, plantillas y macros en mql5, sutilezas y usos

 
Los comentarios que no están relacionados con "Características, complejidades y trucos del lenguaje mql5" han sido trasladados a este hilo.
 
fxsaber:

Este es el comportamiento estándar de MQL5: las variables estáticas comienzan a funcionar después de las variables globales.

Realmente puedes meterte en muchos problemas por esto.

La cuestión de este "comportamiento estándar" aún no se ha resuelto de forma universal, ¿cómo abordarla? Hasta ahora, la única forma que veo es sustituir todas las variables estáticas por globales (los MQ's obligan directamente a globalizar todo)). Pero no se puede hacer en plantillas y macros.

Los campos estáticos de la plantilla también se inicializan después de las variables globales.

 

En general, el problema está resuelto. Utilizando una bandera estática adicional, comprobamos si nuestra variable está inicializada. Si no, la buscamos en la lista de valores guardados previamente. Si no está ahí, lo añadimos. Todo está envuelto en una 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 es el comportamiento estándar de MQL5: las variables estáticas se inician después de las globales.

Uno puede meterse en muchos problemas por ello.

En MQL las variables estáticas se inicializan en la pila global, no por lugar de declaración, como es en C++.

En mi opinión da igual lo que se inicialice primero, estático o global (salvo por el gusto) - en cualquier caso habrá sufridores.

Sería más correcto inicializar primero las variables estáticas y globales que se inicializan con constantes, y luego el resto en el orden de detección por parte del compilador (ya está en el plan, pero lamentablemente no en el calendario).

Y este orden es diferente al de C++.
Condicionalmente, la compilación en MQL se realiza en dos pases: primero se recogen todas las definiciones globales, y luego se realiza la compilación de los cuerpos de las funciones - esta es la razón por la que las variables estáticas se añaden al pool después de las globales

 
Ilyas:

(esto ya está en el plan, pero lamentablemente no en el calendario).

Y me parece que esta cuestión es prioritaria. Porque la situación actual viola la lógica de la ejecución del programa, lo que es simplemente inaceptable para un lenguaje de programación. Y todo tipo de trucos y nuevas funciones son de importancia secundaria.
 

He afinado el código, incluso para los arrays estáticos. Esta vez lo adjunto como archivo.

Hay 4 macros para usar en el código:

1) STATIC_SET(var, data) - asigna a la variable estática var el valor data (mediante operator=), o copia los datos de la matriz en la matriz estática 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 la variable var con datos (ya sea a través del constructor o del operador=), o inicializa la matriz var con constantes - puestas entre llaves y opcionalmente encerradas entre corchetes normales:

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

3) STATIC(type, var, value) - declaración e inicialización de una variable estática:

STATIC(int, a, 10);

4) STATIC_ARR(type, var, values) - declaración e inicialización de un array estático:

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


En los dos primeros puntos, hay que tener en cuenta que el tamaño declarado del array estático no debe ser menor que el número de valores de inicialización, de lo contrario habrá un error.

Y no se pueden inicializar arrays dinámicos con macros. Su tamaño no cambiará hasta que el inicializador regular llegue a la función.

Archivos adjuntos:
StaticVar.mqh  12 kb
 
Y en cuanto a las preguntas sobre por qué necesitamos todo esto, permítanme explicarlo: es para asegurarnos de que nuestro código funciona correctamente, exactamente como se supone que debe hacerlo el algoritmo, y no como los desarrolladores de MQL o cualquier otra persona quería que funcionara.
 
Alexey Navoykov:
En cuanto a las preguntas sobre por qué necesitamos todo esto, lo aclaro: es para que nuestro código funcione correctamente, exactamente como se supone que debe hacerlo el algoritmo, no como querían los desarrolladores de MQL o cualquier otra persona.

¿Puede mostrarnos un ejemplo en el que todo esto pueda simplificar o acortar la escritura de código, o al menos evitar errores? Y por favor, no con funciones abstractas, sino lo más parecido a la realidad del trading en un EA o indicador.

 
Alexey Viktorov:

Y puedes mostrarnos un ejemplo en el que todo esto pueda simplificar la escritura de código, reducirlo o al menos protegernos de los errores. Y por favor, no con funciones abstractas, sino lo más parecido a la realidad del trading, en un EA o indicador.

¿Quiere decir lo más cerca posible? ¿Quieres que escriba ese código específicamente para ti, o que publique mis proyectos? No es necesario.

Aquí, tenemos un objeto global de programa o EA: CExpert Expert; o CProgram Program; Naturalmente se inicializa de alguna manera internamente por defecto (incluyendo todos los objetos internos, de los cuales hay muchos), tal vez en algún lugar se utilizan para este funciones globales auxiliares, y estas funciones pueden contener variables estáticas.Además, se utilizan clases que tienen campos estáticos, que también se relacionan con variables estáticas, y por lo tanto el trabajo de estas clases depende de los valores de los campos estáticos. Valores de campo incorrectos significan objeto de clase incorrectamente inicializado. Siga usted mismo con esta cadena lógica. Y lo peor es que sólo nos enteramos al ejecutar el programa.

No he inventado todo esto desde cero. A menudo me encuentro con punteros rotos que deberían haber sido inicializados o arrays sin rellenar que deberían haber sido inicializados con valores. Y estoy cansado de escarbar constantemente en estos pequeños detalles y modificar el código para satisfacer el algoritmo de inicialización aceptado por los desarrolladores de MQ.

De hecho, se trata de un error, y nada más. Si los desarrolladores han adoptado una determinada secuencia de inicialización de variables, el código debe ejecutarse de acuerdo con esta secuencia, en lugar de saltársela.

Si todo esto te resulta desconocido y no utilizas la funcionalidad descrita y escribes, por ejemplo, al estilo de Peter Konov, entonces que te vaya bien, estos problemas no te han tocado, enhorabuena por ello.

 
Alexey Navoykov:

No he inventado todo esto desde cero. A menudo me encuentro con punteros rotos, que deberían haber sido inicializados, o arrays sin rellenar, que deberían haber sido inicializados por valores. Y escarbar constantemente en estas pequeñas cosas y cambiar el código para ajustarse al algoritmo de inicialización, adoptado por los desarrolladores de MQ, es molesto.

He tenido muchas situaciones de declaración de campos estáticos en clases que se inicializan globalmente (antes de OnInit) y en caso de declaración repetida del campo estático justo después de la descripción de la clase y antes de la declaración de la variable global de su instancia - nunca he tenido problemas con la inicialización del campo estático (ya que en este caso se considera global y se inicializa antes de la instancia de la clase según entiendo). Así que sólo hay que rechazar la declaración de variables estáticas dentro de los métodos y funciones y no hay problema.

 
Ilya Malev:

He tenido muchas situaciones de declarar campos estáticos en clases que se inicializan globalmente (antes de OnInit), siempre y cuando se vuelva a declarar el campo estático justo después de la descripción de la clase, y antes de declarar la variable global de su instancia, nunca hubo ningún problema con la inicialización del campo estático (porque en este caso se considera global y se inicializa antes que la instancia de la clase según tengo entendido). Por lo tanto, sólo hay que rechazar la declaración de variables estáticas dentro de los métodos y funciones y no hay ningún problema.

Por supuesto, no soy muy bueno con la POO, así que no puedo explicarlo claramente, pero aun así quiero corregir tu afirmación. No puedes negarte completamente a declarar variables estáticas dentro de métodos y funciones, pero al menos, no puedes inicializar otras variables estáticas con esos métodos o funciones que contienen variables estáticas.

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

void OnTick()
{
 static int b=a(9);
}
En este ejemplo, la variable estática int b se inicializa primero pero la variable estática int f dentro de la función int a(int n) no se inicializa todavía y como resultado obtenemos un galimatías.
Razón de la queja: