mql5中的OOP、模板和宏,细微之处和用途

 
"mql5语言特点、复杂性和 技巧 "无关的评论已被移至本主题。
 
fxsaber:

这是标准的MQL5行为:静态变量在全局变量之后开始工作。

你真的会因为这个而陷入很多麻烦。

这种 "标准行为 "的问题还没有得到普遍解决,如何处理?到目前为止,我看到的唯一方法是用全局变量取代所有静态变量(MQ的直接强制全局化一切))。但在模板和宏中无法做到这一点。

静态模板字段也在全局变量之后被初始化。

 

一般来说,问题已经解决了。 使用一个额外的静态标志,我们检查我们的变量是否被初始化了。 如果没有,我们就在先前保存的值的列表中寻找它。如果它不在那里,我们就把它加到那里。一切都被包裹在一个宏中STATIC_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();
}

结果。

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

 
fxsaber:

这是MQL5的标准行为:静态变量在全局变量之后启动。

人们会因此而陷入很多麻烦。

在MQL中,静态变量在全局堆栈中被初始化,而不是像C++中那样,通过声明的地方来初始化。

在我看来,先初始化什么,静态还是全局,没有什么区别(除了品味)--在任何情况下都会有受害者。

更正确的做法是先初始化用常量初始化的静态和全局变量,然后按照编译器检测的顺序初始化所有其他变量(这已经在计划中了,但不幸的是没有在日历中)。

而这个顺序与C++的顺序不同。
从条件上讲,MQL的编译是分两次进行的:首先收集所有的全局定义,然后进行函数体的编译--这就是为什么静态变量在全局定义之后被添加到池中的原因。

 
Ilyas:

(这已经在计划中,但不幸的是没有在日历中)。

而在我看来,这个问题是一个优先事项。因为目前的情况违反了程序执行的 逻辑,这对一种编程语言来说是根本无法接受的。 而各种技巧和新功能都是次要的。
 

我对代码进行了微调,包括静态数组的代码。 这次我把它作为一个文件附上。

有4个宏供在代码中使用。

1) STATIC_SET(var, data) - 给静态变量var赋值data(通过operator=),或者将数组数据复制到 静态数组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) - 用数据初始化var变量(通过构造函数或operator=),或者用常数初始化var数组--设置在大括号内,也可以用大括号括起来。

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

3) STATIC(type, var, value) - 声明和初始化一个静态变量

STATIC(int, a, 10);

4) STATIC_ARR(type, var, values) - 声明和初始化一个静态数组。

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


在前两点中,你需要考虑静态数组的声明大小不应小于初始化值的数量,否则会出现错误。

而且你不能用宏来初始化动态数组,它们的大小在常规初始化器到达函数之前不会改变。

附加的文件:
StaticVar.mqh  12 kb
 
至于为什么我们需要这些的问题,让我解释一下:这是为了确保我们的代码能够正确工作,完全按照算法的方式工作,而不是按照MQL开发人员或其他任何人想要的方式工作。
 
Alexey Navoykov:
至于为什么我们需要这一切的问题,我澄清:我们需要它,以便我们的代码能够正常工作,完全按照算法的设计方式,而不是按照MQL开发人员或其他任何人想要的方式工作。

你能告诉我们一个例子吗,当所有这些可以简化或缩短代码编写,或至少可以防止错误。而且,请不要用抽象的功能,而要尽可能地接近EA或指标的交易实际情况。

 
Alexey Viktorov:

而且你可以给我们看一个例子,当这一切可以简化写代码,减少代码,或者至少可以保护我们不出错。而且,请不要用抽象的功能,而是在EA或指标中尽可能地接近交易的实际情况。

你是说尽可能地接近?你想让我专门为你写这样的代码,还是发布我的项目? 我不需要。

在这里,我们有一个程序或EA的全局对象:CExpert Expert;或CProgram Program;它自然是以某种方式在内部默认初始化的(包括所有的内部对象,其中有很多),也许某个地方是用于这个辅助的全局函数,这些函数可能包含静态变量。此外,还使用了有静态字段的类,这些类也与静态变量有关,因此这些类的工作取决于静态字段的值。不正确的字段值意味着不正确的初始化类对象。 自己继续这个逻辑链。 而最糟糕的是,我们只有在执行程序时才会了解到。

我并没有从头开始发明这一切。 我经常遇到本应被初始化的破损指针或本应被初始化为数值的未填充数组。 而且我已经厌倦了不断挖掘这些小细节并修改代码来满足MQ开发者所接受的初始化算法。

事实上--它是一个错误,而不是别的。如果开发者采用了一定的变量初始化 顺序,那么代码就应该按照这个顺序执行,而不是绕过它。

如果这一切对你来说是陌生的,而且你不使用描述的功能,并以彼得-科诺夫的风格写作,那么好了,这些问题没有触及你,对此表示祝贺。

 
Alexey Navoykov:

我并没有从头开始发明这一切。 我经常遇到破损的指针,它应该被初始化,或者未填充的数组,它应该被值初始化。 而不断挖掘这些小东西,改变代码以适应MQ开发者采用的初始化算法,是令人讨厌的。

我有很多在全局初始化的类中声明静态字段的情况(在OnInit之前),以及在类的描述之后和实例的全局变量 声明之前重复声明静态字段的情况--在静态字段初始化方面从未出现过任何问题(因为在这种情况下,它被认为是全局的,在我理解的类的实例之前被初始化)。所以你只需要拒绝在方法和函数里面声明静态变量就没有问题了。

 
Ilya Malev:

我遇到过很多在全局初始化的类中声明静态字段的情况(在OnInit之前),只要你在类描述之后,在声明其实例的全局变量 之前重新声明静态字段,静态字段的初始化就不会有任何问题(因为在这种情况下,按照我的理解,它被认为是全局的,在类实例之前初始化)。所以,你只需要拒绝在方法和函数里面声明静态变量,就完全没有问题了。

当然,我对OOP不是很在行,所以无法解释清楚,但我还是想纠正你的说法。你可以不完全拒绝在方法和函数中声明静态变量,但至少,你不能用那些包含静态变量的方法或函数初始化其他静态变量。

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

void OnTick()
{
 static int b=a(9);
}
在这个例子中,静态int b变量将首先被初始化,但int a(int n)函数中的静态int f变量还没有被初始化,结果我们得到了胡言乱语。
原因: