OOP, Vorlagen und Makros in mql5, Feinheiten und Anwendungen

 
Kommentare, die sich nicht auf "mql5 language features, intricacies and tricks" beziehen, wurden in dieses Thema verschoben.
 
fxsaber:

Dies ist das Standardverhalten von MQL5: statische Variablen beginnen nach den globalen Variablen zu arbeiten.

Damit kann man sich wirklich eine Menge Ärger einhandeln.

Das Problem dieses "Standardverhaltens" ist noch nicht allgemein gelöst, wie geht man damit um? Bisher sehe ich nur die Möglichkeit, alle statischen Variablen durch globale Variablen zu ersetzen (MQs zwingen direkt dazu, alles zu globalisieren). In Vorlagen und Makros ist dies jedoch nicht möglich.

Statische Vorlagenfelder werden ebenfalls nach globalen Variablen initialisiert.

 

Im Allgemeinen ist das Problem gelöst. Mit einem zusätzlichen statischen Flag prüfen wir, ob unsere Variable initialisiert ist. Wenn nicht, suchen wir sie in der Liste der zuvor gespeicherten Werte. Wenn es dort nicht vorhanden ist, fügen wir es dort hinzu. Alles ist in einem Makro verpacktSTATIC_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();
}

Ergebnis:

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

 
fxsaber:

Dies ist das Standardverhalten von MQL5: statische Variablen werden nach den globalen Variablen gestartet.

Man kann dadurch eine Menge Ärger bekommen.

In MQL werden statische Variablen im globalen Stack initialisiert, nicht an der Deklarationsstelle, wie in C++.

Meiner Meinung nach macht es keinen Unterschied, was man zuerst initialisiert, statisch oder global (außer für den Geschmack) - in jedem Fall wird es Leidtragende geben.

Es wäre korrekter, statische und globale Variablen, die mit Konstanten initialisiert werden, zuerst zu initialisieren, dann alle anderen in der Reihenfolge der Erkennung durch den Compiler (es ist bereits im Plan, aber leider nicht im Kalender).

Und diese Reihenfolge unterscheidet sich von der Reihenfolge in C++.
Bedingt wird die Kompilierung in MQL in zwei Durchgängen durchgeführt: zuerst werden alle globalen Definitionen gesammelt, und dann wird die Kompilierung der Funktionskörper durchgeführt - deshalb werden statische Variablen dem Pool nach den globalen hinzugefügt

 
Ilyas:

(dies steht bereits im Plan, aber leider nicht im Kalender).

Und mir scheint, dass diese Frage vorrangig ist. Denn die derzeitige Situation verstößt gegen die Logik der Programmausführung, was für eine Programmiersprache einfach inakzeptabel ist. Und alle möglichen Tricks und neuen Funktionen sind zweitrangig.
 

Ich habe den Code verfeinert, auch für statische Arrays, und hänge ihn diesmal als Datei an.

Es gibt 4 Makros zur Verwendung im Code:

1) STATIC_SET(var, data) - weist der statischen Variable var den Wert data zu (über operator=), oder kopiert die Array-Daten in das statische Array 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) - initialisiert die Variable var mit Daten (entweder über den Konstruktor oder operator=), oder initialisiert das Array var mit Konstanten, die in geschweiften Klammern gesetzt und optional mit normalen Klammern umschlossen werden:

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

3) STATIC(type, var, value) - Deklaration und Initialisierung einer statischen Variablen:

STATIC(int, a, 10);

4) STATIC_ARR(type, var, values) - Deklaration und Initialisierung eines statischen Arrays:

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


Bei den ersten beiden Punkten ist zu beachten, dass die deklarierte Größe des statischen Arrays nicht kleiner sein darf als die Anzahl der initialisierenden Werte, da es sonst zu einem Fehler kommt.

Und Sie können dynamische Arrays nicht mit Makros initialisieren, da sich ihre Größe nicht ändert, bis der reguläre Initialisierer die Funktion erreicht.

Dateien:
StaticVar.mqh  12 kb
 
Und was die Frage angeht, warum wir das alles brauchen, so möchte ich es Ihnen erklären: Es geht darum, sicherzustellen, dass unser Code korrekt funktioniert, genau so, wie der Algorithmus es soll, und nicht so, wie die MQL-Entwickler oder irgendjemand anderes es wollte.
 
Alexey Navoykov:
Was die Frage angeht, warum wir das alles brauchen, so möchte ich klarstellen: Es geht darum, dass unser Code richtig funktioniert, und zwar genau so, wie es der Algorithmus soll, und nicht so, wie es die MQL-Entwickler oder irgendjemand anders wollte.

Können Sie uns ein Beispiel nennen, bei dem all dies das Schreiben von Code vereinfachen oder verkürzen oder zumindest Fehler vermeiden könnte? Und bitte nicht mit abstrakten Funktionen, sondern möglichst nah an der Realität des Handels in einem Expert Advisor oder Indikator.

 
Alexey Viktorov:

Und Sie können uns ein Beispiel zeigen, wann all dies das Schreiben von Code vereinfachen, reduzieren oder uns zumindest vor Fehlern schützen könnte. Und bitte nicht mit abstrakten Funktionen, sondern so nah wie möglich an der Realität des Handels, in einem EA oder Indikator.

Sie meinen, so nah wie möglich? Möchten Sie, dass ich einen solchen Code speziell für Sie schreibe oder meine Projekte einstelle? Das brauche ich nicht.

Hier haben wir ein globales Objekt von Programm oder EA: CExpert Expert; oder CProgram Program; Es ist natürlich irgendwie intern standardmäßig initialisiert (einschließlich aller internen Objekte, von denen es viele gibt), vielleicht irgendwo sind für diese zusätzlichen globalen Funktionen verwendet, und diese Funktionen können statische Variablen enthalten.Außerdem werden Klassen mit statischen Feldern verwendet, die sich auch auf statische Variablen beziehen, so dass die Arbeit dieser Klassen von den Werten der statischen Felder abhängt. Falsche Feldwerte bedeuten ein falsch initialisiertes Klassenobjekt. Machen Sie selbst mit dieser logischen Kette weiter. Und das Schlimmste ist, dass wir es erst beim Ausführen des Programms erfahren.

Ich habe nicht alles von Grund auf neu erdacht. Ich stoße oft auf kaputte Zeiger, die initialisiert werden sollten, oder auf nicht gefüllte Arrays, die mit Werten initialisiert werden sollten. Und ich bin es leid, ständig in diesen kleinen Details zu wühlen und den Code zu ändern, um den von den MQ-Entwicklern akzeptierten Initialisierungsalgorithmus zu erfüllen.

In der Tat ist es ein Fehler und nichts anderes. Wenn die Entwickler eine bestimmte Reihenfolge für die Initialisierung von Variablen festgelegt haben, sollte der Code in Übereinstimmung mit dieser Reihenfolge ausgeführt werden, anstatt sie zu umgehen.

Wenn Ihnen das alles fremd ist und Sie die beschriebenen Funktionen nicht nutzen, sondern z.B. im Stil von Peter Konov schreiben, dann gute Besserung, diese Probleme haben Sie nicht berührt und herzlichen Glückwunsch.

 
Alexey Navoykov:

Ich habe das alles nicht umsonst erfunden. Oft stoße ich auf kaputte Zeiger, die hätten initialisiert werden sollen, oder auf nicht gefüllte Arrays, die mit Werten hätten initialisiert werden sollen. Und es ist lästig, ständig in diesen Kleinigkeiten zu wühlen und den Code neu zu ordnen, damit er zum Initialisierungsalgorithmus passt, den die MQ-Entwickler übernommen haben.

Ich habe viele Situationen der statischen Felder Deklaration in Klassen, die global initialisieren (vor OnInit) und im Falle der wiederholten Deklaration des statischen Feldes direkt nach der Klasse Beschreibung und vor der Deklaration der globalen Variable seiner Instanz keine Probleme mit statischen Feld Initialisierung (weil in diesem Fall es als global und initialisiert vor Instanz der Klasse, wie ich verstehe). Sie müssen also nur darauf verzichten, statische Variablen innerhalb von Methoden und Funktionen zu deklarieren, und schon gibt es kein Problem mehr.

 
Ilya Malev:

Ich habe viele Situationen der Deklaration von statischen Feldern in Klassen, die global (vor OnInit) initialisiert werden, solange Sie statische Feld direkt nach Klassenbeschreibung und vor der Deklaration der globalen Variable seiner Instanz neu deklarieren, gab es nie ein Problem mit statischen Feldinitialisierung (weil in diesem Fall es als global und initialisiert vor der Klasse Instanz, wie ich verstehe). Sie brauchen also nur die Deklaration statischer Variablen innerhalb von Methoden und Funktionen zu verweigern, und es gibt überhaupt kein Problem.

Natürlich kenne ich mich mit OOP nicht sehr gut aus, so dass ich es nicht klar erklären kann, aber dennoch möchte ich Ihre Aussage korrigieren. Sie können die Deklaration von statischen Variablen innerhalb von Methoden und Funktionen nicht vollständig ablehnen, aber Sie dürfen zumindest keine anderen statischen Variablen mit den Methoden oder Funktionen initialisieren, die statische Variablen enthalten.

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

void OnTick()
{
 static int b=a(9);
}
In diesem Beispiel wird die statische Variable int b zuerst initialisiert, aber die statische Variable int f innerhalb der Funktion int a(int n) wird noch nicht initialisiert, und als Ergebnis erhalten wir Kauderwelsch.