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++のように宣言場所ではなく、グローバルスタックで初期化されます。

私の意見では、staticとglobalのどちらを先に初期化しても違いはありません(好みは別として) - いずれにせよ、被害を受ける人はいます。

定数で初期化されるスタティック変数やグローバル 変数を先に初期化し、その後コンパイラが検出した順に残りを全て初期化する方が正しいでしょう(計画には既にありますが、残念ながらカレンダーにはありません)。

そしてこの順番は、C++の順番とは異なります。
条件付きで、MQLのコンパイルは2パスで行われます。最初にすべてのグローバル定義が収集され、次に関数本体のコンパイルが行われます。

 
Ilyas:

(これはすでに計画には入っていますが、残念ながらカレンダーには入っていません)。

そして、この問いが優先されるように思います。なぜなら、現状はプログラム実行の 論理に反しており、プログラミング言語としては到底受け入れられないからです。 また、いろいろなトリックや新機能は二の次です。
 

静的配列の場合など、コードを微調整しました。 今回は、ファイルとして添付します。

コード内で使用するマクロは4つです。

1) STATIC_SET(var, data) - 静的変数varに値dataを代入(operator=経由)、または配列dataを 静的配列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}));


最初の2点では、静的配列の宣言サイズは初期化する値の数より小さくてはならないことを考慮する必要があり、さもなければエラーになります。

また、動的配列はマクロで初期化できないので、通常のイニシャライザが関数に到達するまではサイズが変わりません。

ファイル:
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の前)でstaticフィールドを宣言する場面は多々ありますが、クラスの記述直後、そのインスタンスのグローバル変数を 宣言する前にstaticフィールドを再宣言すれば、staticフィールドの初期化に問題はありませんでした(この場合、グローバルとみなされるので、クラスインスタンスの前に初期化すると理解されているからです)。ですから、メソッドや関数内でのスタティック変数の宣言を拒否 すればよいだけで、全く問題はありません。

もちろん、私はOOPが得意ではないので、明確に説明することはできませんが、それでもあなたの発言を訂正したいと思います。メソッドや関数の内部で静的変数を宣言することを完全に拒否することはできませんが、少なくとも、静的変数を含むメソッドや関数で他の静的変数を初期化することは禁止されています。

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

void OnTick()
{
 static int b=a(9);
}
この例では、static int b 変数は最初に初期化されますが、int a(int n) 関数内の static int f 変数はまだ初期化されておらず、結果としてちんぷんかんぷんな結果になります。
理由: