OOP, templates et macros dans mql5, subtilités et utilisations

 
Les commentaires sans rapport avec "mql5 language features, intricacies and tricks" ont été déplacés vers ce fil de discussion.
 
fxsaber:

C'est le comportement standard de MQL5 : les variables statiques commencent à fonctionner après les variables globales.

Vous pouvez vraiment avoir de gros problèmes à cause de ça.

La question de ce "comportement standard" n'a pas encore été résolue de manière universelle, comment y faire face ? Jusqu'à présent, le seul moyen que je vois est de remplacer toutes les variables statiques par des variables globales (la MQ oblige directement à tout globaliser)). Mais cela ne peut pas être fait dans les modèles et les macros.

Les champs statiques des modèles sont également initialisés après les variables globales.

 

En général, le problème est résolu. En utilisant un drapeau statique supplémentaire, nous vérifions si notre variable est initialisée. Si ce n'est pas le cas, nous la recherchons dans la liste des valeurs précédemment enregistrées. S'il n'y est pas, nous l'y ajoutons. Tout est enveloppé dans une 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();
}

Résultat :

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

 
fxsaber:

C'est le comportement standard de MQL5 : les variables statiques sont démarrées après les variables globales.

On peut avoir beaucoup d'ennuis à cause de ça.

En MQL, les variables statiques sont initialisées dans la pile globale, et non par l'emplacement de la déclaration, comme c'est le cas en C++.

À mon avis, cela ne fait aucune différence de savoir ce qu'il faut initialiser en premier, statique ou global (sauf pour le goût) - dans tous les cas, il y aura des victimes.

Il serait plus correct d'initialiser d'abord les variables statiques et globales qui sont initialisées avec des constantes, puis toutes les autres dans l'ordre de détection par le compilateur (c'est déjà dans le plan, mais malheureusement pas dans le calendrier).

Et cet ordre est différent de l'ordre du C++.
Conditionnellement, la compilation en MQL se fait en deux passes : d'abord toutes les définitions globales sont collectées, puis la compilation des corps de fonction est effectuée - c'est pourquoi les variables statiques sont ajoutées au pool après les définitions globales.

 
Ilyas:

(ceci est déjà dans le plan, mais malheureusement pas dans le calendrier).

Et il me semble que cette question est prioritaire. Parce que la situation actuelle viole la logique d'exécution du programme, ce qui est tout simplement inacceptable pour un langage de programmation. Et toutes sortes d'astuces et de nouvelles fonctions sont d'une importance secondaire.
 

J'ai affiné le code, y compris pour les tableaux statiques. Cette fois, je le joins en tant que fichier.

Il y a 4 macros à utiliser dans le code :

1) STATIC_SET(var, data) - affecte à la variable statique var la valeur data (via l'opérateur=), ou copie les données du tableau dans le tableau statique 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) - initialise la variable var avec des données (soit via le constructeur ou l'opérateur=), ou initialise le tableau var avec des constantes - placées entre des crochets et éventuellement entre des crochets normaux :

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

3) STATIC(type, var, value) - déclaration et initialisation d'une variable statique:

STATIC(int, a, 10);

4) STATIC_ARR(type, var, values) - déclaration et initialisation d'un tableau statique :

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


Dans les deux premiers points, vous devez considérer que la taille déclarée du tableau statique ne doit pas être inférieure au nombre de valeurs d'initialisation, sinon il y aura une erreur.

Et vous ne pouvez pas initialiser des tableaux dynamiques avec des macros. Leur taille ne changera pas jusqu'à ce que l'initialisateur régulier arrive à la fonction.

Dossiers :
StaticVar.mqh  12 kb
 
Quant à la question de savoir pourquoi nous avons besoin de tout cela, laissez-moi vous expliquer : c'est pour s'assurer que notre code fonctionne correctement, exactement de la manière dont l'algorithme est censé fonctionner, et non de la manière dont les développeurs de MQL ou quiconque voulait qu'il fonctionne.
 
Alexey Navoykov:
Quant aux questions sur la raison pour laquelle nous avons besoin de tout cela, je clarifie : c'est pour que notre code fonctionne correctement, exactement de la manière dont l'algorithme est censé fonctionner, et non de la manière dont les développeurs de MQL ou quiconque le voulait.

Pouvez-vous nous montrer un exemple où tout ceci pourrait simplifier ou raccourcir l'écriture du code, ou au moins éviter les erreurs. Et s'il vous plaît, pas avec des fonctions abstraites, mais aussi proches que possible des réalités du trading dans un EA ou un indicateur.

 
Alexey Viktorov:

Et vous pouvez me montrer un exemple où tout cela pourrait simplifier l'écriture du code, le réduire ou au moins le protéger des erreurs. Et s'il vous plaît, pas avec des fonctions abstraites, mais au plus près des réalités du trading, dans un EA ou un indicateur.

Vous voulez dire aussi près que possible ? Voulez-vous que j'écrive ce code spécifiquement pour vous, ou que j'affiche mes projets ? Ce n'est pas nécessaire.

Ici, nous avons un objet global de programme ou EA : CExpert Expert ; ou CProgram Program ; Il est naturellement initialisé d'une manière ou d'une autre en interne par défaut (y compris tous les objets internes, dont il y a beaucoup), peut-être quelque part sont utilisés pour cette fonctions globales auxiliaires, et ces fonctions peuvent contenir des variables statiques.De plus, on utilise des classes qui ont des champs statiques, qui se rapportent également à des variables statiques, et donc le travail de ces classes dépend des valeurs des champs statiques. Des valeurs de champs incorrectes signifient un objet de classe mal initialisé. Continuez vous-même cette chaîne logique. Et le pire, c'est que nous ne l'apprenons qu'en exécutant le programme.

Je n'ai pas tout pensé à partir de zéro. Je rencontre souvent des pointeurs cassés qui devraient être initialisés ou des tableaux non remplis qui devraient être initialisés avec des valeurs. Et je suis fatigué de creuser constamment dans ces petits détails et de modifier le code pour satisfaire l'algorithme d'initialisation accepté par les développeurs de MQ.

En fait - c'est un bug, et rien d'autre. Si les développeurs ont adopté une certaine séquence d'initialisation des variables, alors le code doit être exécuté conformément à cette séquence, plutôt que de la contourner.

Si tout cela ne vous est pas familier et que vous n'utilisez pas les fonctionnalités décrites, mais écrivez par exemple dans le style de Peter Konov, alors bon débarras, ces problèmes ne vous ont pas touché et félicitations.

 
Alexey Navoykov:

Je n'ai pas inventé tout cela à partir de rien. Je tombe souvent sur des pointeurs cassés, qui auraient dû être initialisés, ou des tableaux non remplis, qui auraient dû être initialisés par des valeurs. Et creuser constamment dans ces petites choses et réarranger le code pour l'adapter à l'algorithme d'initialisation, adopté par les développeurs de MQ, est ennuyeux.

J'ai eu de nombreuses situations de déclaration de champs statiques dans des classes qui s'initialisent globalement (avant OnInit) et dans le cas de déclaration répétée de champ statique juste après la description de la classe et avant la déclaration de la variable globale de son instance - je n'ai jamais eu de problèmes avec l'initialisation du champ statique (car dans ce cas il est considéré comme global et initialisé avant l'instance de la classe d'après ce que je comprends). Il suffit donc de refuser de déclarer des variables statiques à l'intérieur des méthodes et des fonctions et il n'y a aucun problème.

 
Ilya Malev:

J'ai eu de nombreuses situations de déclaration de champs statiques dans des classes qui sont initialisées globalement (avant OnInit), tant que vous redéclarez le champ statique juste après la description de la classe, et avant de déclarer la variable globale de son instance, il n'y a jamais eu de problème avec l'initialisation du champ statique (parce que dans ce cas il est considéré comme global et initialisé avant l'instance de la classe comme je le comprends). Il suffit donc de refuser la déclaration des variables statiques à l'intérieur des méthodes et des fonctions et il n'y a aucun problème.

Bien sûr, je ne suis pas très doué pour la POO, donc je ne peux pas l'expliquer clairement, mais je veux quand même corriger votre déclaration. Vous ne pouvez pas refuser complètement de déclarer des variables statiques à l'intérieur des méthodes et des fonctions, mais au moins, vous ne pouvez pas initialiser d'autres variables statiques avec ces méthodes ou fonctions qui contiennent des variables statiques.

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

void OnTick()
{
 static int b=a(9);
}
Dans cet exemple, la variable static int b sera initialisée en premier mais la variable static int f dans la fonction int a(int n) n'est pas encore initialisée et le résultat est un charabia.
Raison: