OOP, templates and macros in mql5, subtleties and uses

 
Comments not related to "mql5 language features, intricacies and tr icks" have been moved to this thread.
 
fxsaber:

This is standard MQL5 behaviour: static variables start working after global variables.

You can really get into a lot of trouble because of this.

The issue of this "standard behavior" has not been solved universally yet, how to deal with it? So far, the only way I can see is to replace all static variables with global ones (MQ's directly force to globalize everything)). But it cannot be done in templates and macros.

Static template fields are also initialized after global variables.

 

In general, the problem is solved. Using an additional static flag, we check if our variable is initialized. If not, we look for it in the list of previously saved values. If it is not there, we add it there. Everything is wrapped in a 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();
}

Result:

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

 
fxsaber:

This is the standard behavior of MQL5: static variables are started after the global ones.

One can get in a lot of trouble because of it.

In MQL static variables are initialized in global stack, not by declaration place, as it is in C++.

In my opinion it makes no difference what to initialize first, static or global (except for taste) - in any case there will be sufferers.

It would be more correct to initialize static and global variables which are initialized with constants first, then all the rest in the order of detection by compiler (it is already in the plan, but unfortunately not in the calendar).

And this order is different from the order of C++.
Conditionally, the compilation in MQL is done in two passes: first all global definitions are collected, and then the compilation of function bodies is done - this is why static variables are added to the pool after the global

 
Ilyas:

(this is already in the plan, but unfortunately not in the calendar).

And it seems to me that this question is a priority. Because the current situation violates the logic of program execution, which is simply unacceptable for a programming language. And all sorts of tricks and new functions are of secondary importance.
 

I have fine-tuned the code, including for static arrays. This time I am attaching it as a file.

There are 4 macros for use in the code:

1) STATIC_SET(var, data) - assigns to the static variable var the value data (via operator=), or copies the array data into the static 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) - initializes the var variable with data (either via constructor or operator=), or initializes the var array with constants - set within curly brackets and optionally enclosed with normal brackets:

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

3) STATIC(type, var, value) - declaration and initialization of a static variable:

STATIC(int, a, 10);

4) STATIC_ARR(type, var, values) - declaration and initialization of a static array:

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


In the first two points, you need to consider that the declared size of static array should not be less than the number of initializing values, otherwise there will be an error.

And you can't initialize dynamic arrays with macros. Their size won't change until the regular initializer gets to the function.

Files:
StaticVar.mqh  12 kb
 
And as for the questions about why we need all this, let me explain: it's to make sure that our code works correctly, exactly the way the algorithm is supposed to, and not the way the MQL developers or anyone else wanted it to work.
 
Alexey Navoykov:
As for the questions about why we need all this, I clarify: it's to make our code work properly, exactly the way the algorithm is supposed to, not the way MQL developers or anyone else wanted it to.

Can you show us an example when all this could simplify or shorten code writing, or at least prevent errors. And please, not with abstract functions, but as close to the realities of trading in an Expert Advisor or indicator as possible.

 
Alexey Viktorov:

And you can show me an example when all this could simplify writing code, reduce it or at least protect from errors. And please, not with abstract functions, but as close as possible to the realities of trading, in an EA or indicator.

You mean as close as possible? Do you want me to write such code specifically for you, or post my projects? I don't need to.

Here, we have a global object of program or EA: CExpert Expert; or CProgram Program; It is naturally initialized somehow inside by default (including all internal objects, of which there are many), perhaps somewhere are used for this auxiliary global functions, and these functions may contain static variables.Also, classes that have static fields are used, which also relate to static variables, and therefore the work of these classes depends on the values of static fields. Incorrect field values mean incorrectly initialized class object. Go on with this logical chain yourself. And the worst thing is that we learn about it only when executing the program.

I haven't thought it all up from scratch. I often come across broken pointers which should be initialized or unfilled arrays which should be initialized with values. And I'm tired of constant digging in these small details and modifying code to satisfy the initialization algorithm accepted by MQ developers.

In fact - it is a bug, and nothing else. If the developers have adopted a certain sequence of initialization of variables, then the code should be executed in accordance with this sequence, rather than bypassing it.

If all this is unfamiliar to you and you don't use described functionality and write for example in Peter Konov style, then good riddance, these problems haven't touched you, congratulations on that.

 
Alexey Navoykov:

I didn't invent all this from nothing. Often I come across broken pointers, which should have been initialized, or unfilled arrays, which should have been initialized by values. And constantly digging in these little things and changing code to fit the initialization algorithm, adopted by MQ developers, is annoying.

I've had lots of situations of static fields declaration in classes which initialized globally (before OnInit) and in case of repeated declaration of static field right after class description and before declaration of global variable of its instance - never had any problems with static field initialization (as in this case it is considered global and initialized before instance of class as I understand). So you just need to refuse declaring static variables inside methods and functions and there's no problem.

 
Ilya Malev:

I've had many situations of declaring static fields in classes which are initialized globally (before OnInit), as long as you re-declare static field right after class description, and before declaring global variable of its instance, there was never any problem with static field initialization (because in this case it is considered global and initialized before the class instance as I understand). So, you just need to refuse declaration of static variables inside methods and functions and there's no problem at all.

Of course, I'm not very good with OOP, so I cannot explain it clearly, but still I want to correct your statement. You may not completely refuse declaring static variables inside methods and functions, but at least, you may not initialize other static variables with those methods or functions which contain static variables.

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

void OnTick()
{
 static int b=a(9);
}
In this example, static int b variable will be initialized first but static int f variable inside int a(int n) function will not be initialized yet and as the result we will get gibberish.
Reason: