OOP, modelli e macro in mql5, sottigliezze e usi

 
I commenti non relativi a "caratteristiche, complessità e trucchi del linguaggio mql5" sono stati spostati in questo thread.
 
fxsaber:

Questo è il comportamento standard di MQL5: le variabili statiche iniziano a lavorare dopo le variabili globali.

Si può davvero finire in un sacco di guai a causa di questo.

Il problema di questo "comportamento standard" non è stato ancora risolto universalmente, come affrontarlo? Finora, l'unico modo che posso vedere è quello di sostituire tutte le variabili statiche con quelle globali (MQ's costringe direttamente a globalizzare tutto)). Ma non può essere fatto in modelli e macro.

Anche i campi statici dei template sono inizializzati dopo le variabili globali.

 

In generale, il problema è risolto. Usando un flag statico aggiuntivo, controlliamo se la nostra variabile è inizializzata. Se non lo è, la cerchiamo nella lista dei valori precedentemente salvati. Se non c'è, lo aggiungiamo lì. Tutto è avvolto in 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();
}

Risultato:

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

 
fxsaber:

Questo è il comportamento standard di MQL5: le variabili statiche sono avviate dopo quelle globali.

Si può finire in un sacco di guai per questo.

In MQL le variabili statiche sono inizializzate nello stack globale, non per luogo di dichiarazione, come in C++.

Secondo me non fa differenza cosa inizializzare prima, statico o globale (tranne che per il gusto) - in ogni caso ci sarà chi soffre.

Sarebbe più corretto inizializzare prima le variabili statiche e globali che sono inizializzate con le costanti, poi tutto il resto nell'ordine di rilevamento da parte del compilatore (è già nel piano, ma purtroppo non nel calendario).

E questo ordine è diverso da quello del C++.
Condizionatamente, la compilazione in MQL è fatta in due passaggi: prima vengono raccolte tutte le definizioni globali, e poi viene fatta la compilazione dei corpi delle funzioni - questo è il motivo per cui le variabili statiche vengono aggiunte al pool dopo le globali

 
Ilyas:

(questo è già nel piano, ma purtroppo non nel calendario).

E mi sembra che questa domanda sia una priorità. Perché la situazione attuale viola la logica di esecuzione del programma, che è semplicemente inaccettabile per un linguaggio di programmazione. E tutti i tipi di trucchi e le nuove funzioni sono di secondaria importanza.
 

Ho messo a punto il codice, anche per gli array statici, e questa volta lo allego come file.

Ci sono 4 macro da usare nel codice:

1) STATIC_SET(var, data) - assegna alla variabile statica var il valore data (tramite operator=), o copia i dati dell'array nell 'array statico 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) - inizializza la variabile var con i dati (sia tramite costruttore o operator=), o inizializza l'array var con le costanti - impostate tra parentesi graffe e opzionalmente racchiuse tra parentesi normali:

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

3) STATIC(type, var, value) - dichiarazione e inizializzazione di una variabile statica:

STATIC(int, a, 10);

4) STATIC_ARR(type, var, values) - dichiarazione e inizializzazione di un array statico:

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


Nei primi due punti, dovete considerare che la dimensione dichiarata dell'array statico non deve essere inferiore al numero di valori di inizializzazione, altrimenti ci sarà un errore.

E non potete inizializzare gli array dinamici con le macro: la loro dimensione non cambierà finché l'inizializzatore regolare non arriva alla funzione.

File:
StaticVar.mqh  12 kb
 
E per quanto riguarda le domande sul perché abbiamo bisogno di tutto questo, lasciatemi spiegare: è per assicurarci che il nostro codice funzioni correttamente, esattamente nel modo in cui l'algoritmo dovrebbe funzionare, e non nel modo in cui gli sviluppatori di MQL o chiunque altro voleva che funzionasse.
 
Alexey Navoykov:
Per quanto riguarda le domande sul perché abbiamo bisogno di tutto questo, chiarisco: è per far funzionare correttamente il nostro codice, esattamente nel modo in cui l'algoritmo dovrebbe funzionare, non nel modo in cui gli sviluppatori di MQL o chiunque altro voleva che funzionasse.

Puoi mostrarci un esempio in cui tutto questo potrebbe semplificare o accorciare la scrittura del codice, o almeno prevenire gli errori. E per favore, non con funzioni astratte, ma il più vicino possibile alla realtà del trading in un EA o indicatore.

 
Alexey Viktorov:

E potete mostrarmi un esempio in cui tutto questo potrebbe semplificare la scrittura del codice, ridurlo o almeno proteggerlo dagli errori. E per favore, non con funzioni astratte, ma il più vicino possibile alla realtà del trading, in un EA o indicatore.

Vuoi dire il più vicino possibile? Volete che scriva questo codice appositamente per voi o che pubblichi i miei progetti? Non ne ho bisogno.

Qui, abbiamo un oggetto globale di programma o EA: CExpert Expert; o CProgram Program; È naturalmente inizializzato in qualche modo internamente di default (compresi tutti gli oggetti interni, di cui ce ne sono molti), forse da qualche parte si usano per questo funzioni globali ausiliarie, e queste funzioni possono contenere variabili statiche.Inoltre, si usano classi che hanno campi statici, che si riferiscono anche a variabili statiche, e quindi il lavoro di queste classi dipende dai valori dei campi statici. Valori di campo errati significano un oggetto di classe inizializzato in modo errato. Continuate voi stessi con questa catena logica. E la cosa peggiore è che ne veniamo a conoscenza solo durante l'esecuzione del programma.

Non ho pensato a tutto da zero. Spesso mi imbatto in puntatori rotti che dovrebbero essere inizializzati o in array non riempiti che dovrebbero essere inizializzati con valori. E sono stanco di scavare costantemente in questi piccoli dettagli e modificare il codice per soddisfare l'algoritmo di inizializzazione accettato dagli sviluppatori di MQ.

Infatti - è un bug, e nient'altro. Se gli sviluppatori hanno adottato una certa sequenza di inizializzazione delle variabili, allora il codice dovrebbe essere eseguito in conformità con questa sequenza, piuttosto che aggirarla.

Se tutto questo non vi è familiare e non usate le funzionalità descritte, ma scrivete per esempio in stile Peter Konov, allora buon viaggio, questi problemi non vi hanno toccato e complimenti.

 
Alexey Navoykov:

Non ho inventato tutto questo dal nulla. Spesso mi imbatto in puntatori rotti, che avrebbero dovuto essere inizializzati, o in array non riempiti, che avrebbero dovuto essere inizializzati da valori. E scavare costantemente in queste piccole cose e riorganizzare il codice per adattarlo all'algoritmo di inizializzazione, adottato dagli sviluppatori MQ, è fastidioso.

Ho avuto molte situazioni di dichiarazione di campi statici in classi che inizializzano globalmente (prima di OnInit) e in caso di dichiarazione ripetuta del campo statico subito dopo la descrizione della classe e prima della dichiarazione della variabile globale della sua istanza - non ho mai avuto problemi con l'inizializzazione del campo statico (poiché in questo caso è considerato globale e inizializzato prima dell'istanza della classe come ho capito). Quindi basta rifiutare di dichiarare variabili statiche all'interno di metodi e funzioni e non ci sono problemi.

 
Ilya Malev:

Ho avuto molte situazioni di dichiarazione di campi statici in classi che sono inizializzate globalmente (prima di OnInit), finché si ri-dichiara il campo statico subito dopo la descrizione della classe, e prima di dichiarare la variabile globale della sua istanza, non c'è mai stato alcun problema con l'inizializzazione del campo statico (perché in questo caso è considerato globale e inizializzato prima dell'istanza della classe come ho capito). Quindi, basta rifiutare la dichiarazione di variabili statiche all'interno di metodi e funzioni e non c'è alcun problema.

Naturalmente, non sono molto bravo con OOP, quindi non posso spiegarlo chiaramente, ma voglio comunque correggere la tua affermazione. Non potete rifiutarvi completamente di dichiarare variabili statiche all'interno di metodi e funzioni, ma almeno non potete inizializzare altre variabili statiche con quei metodi o funzioni che contengono variabili statiche.

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

void OnTick()
{
 static int b=a(9);
}
In questo esempio, la variabile statica int b sarà inizializzata per prima, ma la variabile statica int f all'interno della funzione int a(int n) non è ancora inizializzata e come risultato otterremo delle parole senza senso.
Motivazione: