English Русский Español Português
preview
初級から中級まで:継承

初級から中級まで:継承

MetaTrader 5 |
21 0
CODE X
CODE X

はじめに

前回の「初級から中級まで:構造体(VII)」では、一見するとかなり複雑に見えるものの、最終的には非常に興味深く魅力的な構造化プログラミングの話に入りました。私自身の経験からも分かるのですが、学習の初期段階では、なぜあるコードは動作し、別のコードは動作しないのかを理解するのは非常に困難です。多くの試行錯誤と継続的な努力を経て、ようやく私はここで皆さんに伝えようとしている概念を理解するに至りました。多くの人は、講座を受けたり他人のコードを読むだけでプログラミングを習得できると考えています。

しかし残念ながら、実際にプログラミングを習得するには実践が不可欠です。そして、問題を一つずつ解決し続ける時間を経て初めて、優れた技術者になることができます。ただし、繰り返しになりますが、これには時間がかかります。それでも、学習プロセスを少しでも加速できるのであれば、本当に学びたい人々に知識を共有する価値はあるでしょう。ここがまさに重要なポイントです。

ここから先はさらに興味深い内容になります。しっかりとした基礎を築き、前回までに説明された内容を理解していることが非常に重要です。もし以前の内容を学習し、実践していない場合、この先および今後の記事を理解するのはかなり困難になるでしょう。


単純な継承

よくある誤解の一つは、継承はクラスコード、あるいは一般的にオブジェクト指向プログラミング(OOP)の中にのみ存在するというものです。では、継承とは何でしょうか。簡単に言えば、それは基本となる単純なデータ型を作成し、その上に、それよりも複雑なデータ型を構築することです。その際、すでにその単純なデータ型で実装されている要素、関数、手続きを再び作成する必要はありません。

より理解しやすくするために、次の例を考えてみましょう。自然界には動物、植物、鉱物など多くの種類が存在します。ここで、自然界に存在するすべての入力データをカテゴリごとに整理するコードを作成する必要があるとします。この場合、それらを「動物」「植物」「鉱物」という3つのカテゴリに分類するとします。このような場合、あなたならどう実装するでしょうか。おそらく、入力データを3種類に分類するために、それぞれ別々の構造体を作成することになるでしょう。

このような構造体を作成すると、完全に構造化プログラミングのスタイルに従った場合でも、コードをより扱いやすく、理解しやすくできることに気づくはずです。これは以前の記事でもすでに指摘されています。しかし、動物と植物には共通点があります。どちらも鉱物とは異なり「生物」です。つまり、植物と動物は共通の構造要素を持っています。

これらの共通要素は、新しい「基底構造」にまとめることができます。そうすることで、生物に共通する部分を一箇所に集約でき、データ構造内でのコードの重複を避けることができます。また、将来的な保守性や実装、拡張性も向上します。

一般的に、MetaTrader 5やそれに伴うMQL5を用いた金融市場向けのコードでは、本記事で示すようなレベルの抽象化は必ずしも必要ではありません。本来の用途では、構造化プログラミングやOOPを使う必要すらない場合もあります。

しかし、プログラミングという性質や、今後直面し得る問題の種類を考えると、ここで説明しているメカニズムや概念を理解しておくことは非常に有用かつ重要です。必ずしも「使う」ためではなく、物事の仕組みを理解することで、より少ない労力で開発できるようになるためです。目標を達成するためには、これらの概念を理解することが、たとえ一見複雑に見えたとしても、将来的に大きな簡素化につながります。

繰り返しますが、エキスパートアドバイザー(EA)やインジケーターを作成するために構造化プログラミングを理解する必要はありません。従来の手法でも実装可能です。もし私の言葉を疑うのであれば、最初の記事「一からの取引エキスパートアドバイザーの開発」を確認してください。そこでは構造化プログラミングやOOPを使わずに、チャートへ直接注文を送るシンプルなEAの作成方法を解説しています。

インジケーターについても同様です。記事「初級から中級まで:インジケーター(IV)」では、チャート上にインジケーターを表示する方法を紹介しました。これらはすべてシンプルであり、特別な知識を必要としません。必要なのは基礎知識とMQL5ドキュメントの理解だけです。

「作るために必要な知識」と「必ずしも必要ではない知識」の区別ができたところで、再び仮想的な例に戻りましょう。生物と無生物の間に共通要素が存在することが分かっているため、それらを実装面および関数・手続きの保守性の観点から、論理的かつ一貫した形で分離することができます。

これをより明確にするために、前回の記事で扱ったコードの一部を見てみましょう。同様の結果は以下のように確認できます。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct st_Reg
005. {
006.     //+----------------+
007.     private:
008.     //+----------------+
009.         string  h_value;
010.         uint    k_value;
011.     //+----------------+
012.     public:
013.     //+----------------+
014.         void Set(const uint arg1, const string arg2)
015.         {
016.             k_value = arg1;
017.             h_value = arg2;
018.         }
019.     //+----------------+
020.         uint Get_K(void)    { return k_value; }
021.     //+----------------+
022.         string Get_H(void)  { return h_value; }
023.     //+----------------+
024. };
025. //+------------------------------------------------------------------+
026. struct st_Bio
027. {
028.     //+----------------+
029.     private:
030.     //+----------------+
031.         string  h_value;
032.         string  b_value;
033.         uint    k_value;
034.     //+----------------+
035.     public:
036.     //+----------------+
037.         void Set(const uint arg1, const string arg2, const string arg3)
038.         {
039.             k_value = arg1;
040.             h_value = arg2;
041.             b_value = arg3;
042.         }
043.     //+----------------+
044.         uint Get_K(void)    { return k_value; }
045.     //+----------------+
046.         bool Get_Bio(string &arg1, string &arg2)
047.         {
048.             arg1 = h_value;
049.             arg2 = b_value;
050. 
051.             return true;
052.         }
053.     //+----------------+
054. };
055. //+------------------------------------------------------------------+
056. template <typename T>
057. struct st_Data
058. {
059.     //+----------------+
060.     private:
061.     //+----------------+
062.         T Values[];
063.     //+----------------+
064.     public:
065.     //+----------------+
066.         bool Set(const T &arg)
067.         {
068.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
069.                 return false;
070. 
071.             Values[Values.Size() - 1] = arg;
072. 
073.             return true;
074.         }
075.     //+----------------+
076.         T Get(const uint index)
077.         {
078.             for (uint c = 0; c < Values.Size(); c++)
079.                 if (Values[c].Get_K() == index)
080.                     return Values[c];
081. 
082.             return Values[0];
083.         }
084.     //+----------------+
085. };
086. //+------------------------------------------------------------------+
087. #define PrintX(X) Print(#X, " => [", X, "]")
088. //+------------------------------------------------------------------+
089. void CheckBio(st_Data <st_Bio> &arg)
090. {
091.     string sz[2];
092. 
093.     Print("Checking data in the structure...");
094.     for (uint i = 7; i < 11; i += 3)
095.     {
096.         Print("Index: ", i, " Result: ");
097.         if (arg.Get(i).Get_Bio(sz[0], sz[1]))
098.             ArrayPrint(sz);
099.         else
100.             Print("Failed.");
101.     }
102. }
103. //+------------------------------------------------------------------+
104. void OnStart(void)
105. {
106.     const string T = "possible loss of data due to type conversion";
107.     const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"};
108.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
109. 
110.     st_Data <st_Reg> Info_1;
111.     st_Data <st_Bio> Info_2;
112. 
113.     string  H[];
114. 
115.     StringSplit(T, ' ', H);
116.     for (uint c = 0; c < H.Size(); c++)
117.     {
118.         st_Reg  reg;
119.         st_Bio  bio;
120. 
121.         reg.Set(K[c], H[c]);
122.         bio.Set(K[c], M[c], H[c]);
123. 
124.         Info_1.Set(reg);
125.         Info_2.Set(bio);
126.     }
127. 
128.     PrintX(Info_1.Get(3).Get_H());
129.     PrintX(Info_1.Get(13).Get_H());
130.     CheckBio(Info_2);
131. }
132. //+------------------------------------------------------------------+

コード01

コード01を実行すると、以下の図に示すような結果が得られます。

図01

それでは、本題に入りましょう。動物と植物に共通要素があるように、コード01においてk_valueはst_Regとst_Bioにとって共通の要素です。しかし、これらの構造体は互いに相互作用せず、完全に別個のものです。これは以下の図で視覚的に示されています。

図02

言い換えると、これらは異なるエンティティでありながら、共通の特徴を持っています。このようなコードの重複は、複数の構造体が同じ方法でアクセス・処理される要素を共有している場合に発生します。その場合、分離しない方が自然な場合もあり、コードを不必要に複雑化させてしまいます。

これをより明確にするために、仮にst_Reg構造体に存在するGet_K関数を改善するとします。その結果、共通部分のコードが大幅に改善されたことに気づいたとします。しかし、その変更をst_Bioには反映しなかったとします。両方の構造体を同時に使用した場合、st_BioはGet_Kを使っているにもかかわらず、st_Regとは異なる挙動を示すことに気づきます。

ここから問題が始まります。st_RegにあるGet_Kのコードはst_Bioにもコピーされており、その後さらにGet_Kを修正する必要が出てきた場合、両方の構造体を修正しなければなりません。

これは多大な時間ロスであるだけでなく、コードの改善を著しく困難にします。ここではまだ2つの構造体しか扱っていませんが、実際のコードでは数十、あるいは数百の構造体が同じ要素を共有している可能性があります。そのようなコードをデバッグしたり保守したりすることを想像できますか。それは悪夢のような作業です。しかし、この問題を単純化する方法が存在します。それがまさに「継承」の使用です。

継承とは単純に、この共通部分を両方の構造体から切り出し、別の構造体に移動することです。そして、その構造体がそのコードを継承して利用できるようにする仕組みです。これを聞くと複雑に感じるかもしれません。しかし実際には、想像しているよりもはるかに簡単に実装できます。

それでは実際に、この考え方をコード01に適用し、継承を使う形にしていきます。そのための最初のステップを以下のコードで示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private:
07.         uint KeyValue;
08.     public:
09.     //+----------------+
10.         void SetKey(const uint arg) { KeyValue = arg; }
11.     //+----------------+
12.         uint GetKey(void) { return KeyValue; }
13. };
14. //+------------------------------------------------------------------+
15. struct st_Reg
16. {
17.     private:
18.         string  Value;
19.     public:
20.     //+----------------+
21.         void SetValue(const uint arg1, const string arg2)
22.         {
23.             Value = arg2;
24.         }
25.     //+----------------+
26.         string GetValue(void)  { return Value; }
27. };
28. //+------------------------------------------------------------------+
29. struct st_Bio
30. {
31.     private:
32.         string  Value[2];
33.     public:
34.     //+----------------+
35.         void SetValue(const uint arg1, const string arg2, const string arg3)
36.         {
37.             Value[0] = arg2;
38.             Value[1] = arg3;
39.         }
40.     //+----------------+
41.         bool GetValue(string &arg1, string &arg2)
42.         {
43.             arg1 = Value[0];
44.             arg2 = Value[1];
45. 
46.             return true;
47.         }
48.     //+----------------+
49. };
50. //+------------------------------------------------------------------+
                   .
                   .
                   .

コード02

コード02を見ると、新しい構造体が作成されていることが分かります。これはst_Baseと呼ばれます。ここで注意してください。コード02では変数の整理が改善されているものの、コード01と同じ数および同じ種類の変数が依然として含まれています。しかし、構造体はより整理されています。しかし、ここで最も重要なのは本質的にst_Base構造体そのものです。その後、次のような結果が得られます。

図03

注意してください。これは図02と非常に似ています。しかし図03では継承は使用されていません。これはコード02でも継承が使われていないためです。そのため、コード01をコード02でコンパイルしようとすると、コンパイラはいくつかのエラーを生成します。これを修正するためには、図03を図04へと変換する必要があります。以下に示します。

図04

図04では、矢印が継承の実装方法を示しています。言い換えると、st_Base構造体の内容を大きな変更を加えることなくst_Bioおよびst_Reg構造体へ取り込むことになります。そのために、コード02を以下のコードへと単純に修正します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private:
07.         uint KeyValue;
08.     public:
09.     //+----------------+
10.         void SetKey(const uint arg) { KeyValue = arg; }
11.     //+----------------+
12.         uint GetKey(void) { return KeyValue; }
13. };
14. //+------------------------------------------------------------------+
15. struct st_Reg : public st_Base
16. {
17.     private:
18.         string  Value;
19.     public:
20.     //+----------------+
21.         void SetValue(const uint arg1, const string arg2)
22.         {
23.             Value = arg2;
24.         }
25.     //+----------------+
26.         string GetValue(void)  { return Value; }
27. };
28. //+------------------------------------------------------------------+
29. struct st_Bio : public st_Base
30. {
31.     private:
32.         string  Value[2];
33.     public:
34.     //+----------------+
35.         void SetValue(const uint arg1, const string arg2, const string arg3)
36.         {
37.             Value[0] = arg2;
38.             Value[1] = arg3;
39.         }
40.     //+----------------+
41.         bool GetValue(string &arg1, string &arg2)
42.         {
43.             arg1 = Value[0];
44.             arg2 = Value[1];
45. 
46.             return true;
47.         }
48.     //+----------------+
49. };
50. //+------------------------------------------------------------------+
                   .
                   .
                   .

コード03

コードにおける継承の実装がいかに難しいかを見てください。しかし、コード03から分かるように、まだ解決されていない値への参照が存在しています。これは、このコードでは継承を実装しただけであり、KeyValue情報を適用する形で実際に使用しているわけではないためです。私たちの目的は、st_Dataがデータ同士の関係性を構築し、図01で示されるような構造を形成できるようにすることです。

このような問題を解決するためには、コードを再度修正する必要があります。その結果、コードは以下のようになります。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct st_Base
005. {
006.     private:
007.         uint KeyValue;
008.     public:
009.     //+----------------+
010.         void SetKey(const uint arg) { KeyValue = arg; }
011.     //+----------------+
012.         uint GetKey(void) { return KeyValue; }
013. };
014. //+------------------------------------------------------------------+
015. struct st_Reg : public st_Base
016. {
017.     private:
018.         string  Value;
019.     public:
020.     //+----------------+
021.         void SetValue(const uint arg1, const string arg2)
022.         {
023.             SetKey(arg1);
024.             Value = arg2;
025.         }
026.     //+----------------+
027.         string GetValue(void)  { return Value; }
028. };
029. //+------------------------------------------------------------------+
030. struct st_Bio : public st_Base
031. {
032.     private:
033.         string  Value[2];
034.     public:
035.     //+----------------+
036.         void SetValue(const uint arg1, const string arg2, const string arg3)
037.         {
038.             SetKey(arg1);
039.             Value[0] = arg2;
040.             Value[1] = arg3;
041.         }
042.     //+----------------+
043.         bool GetValue(string &arg1, string &arg2)
044.         {
045.             arg1 = Value[0];
046.             arg2 = Value[1];
047. 
048.             return true;
049.         }
050.     //+----------------+
051. };
052. //+------------------------------------------------------------------+
053. template <typename T>
054. struct st_Data
055. {
056.     private:
057.         T Values[];
058.     public:
059.     //+----------------+
060.         bool Set(const T &arg)
061.         {
062.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
063.                 return false;
064. 
065.             Values[Values.Size() - 1] = arg;
066. 
067.             return true;
068.         }
069.     //+----------------+
070.         T Get(const uint index)
071.         {
072.             for (uint c = 0; c < Values.Size(); c++)
073.                 if (Values[c].GetKey() == index)
074.                     return Values[c];
075. 
076.             return Values[0];
077.         }
078. };
079. //+------------------------------------------------------------------+
080. #define PrintX(X) Print(#X, " => [", X, "]")
081. //+------------------------------------------------------------------+
082. void CheckBio(st_Data <st_Bio> &arg)
083. {
084.     string sz[2];
085. 
086.     Print("Checking data in the structure...");
087.     for (uint i = 7; i < 11; i += 3)
088.     {
089.         Print("Index: ", i, " Result: ");
090.         if (arg.Get(i).GetValue(sz[0], sz[1]))
091.             ArrayPrint(sz);
092.         else
093.             Print("Failed.");
094.     }
095. }
096. //+------------------------------------------------------------------+
097. void OnStart(void)
098. {
099.     const string T = "possible loss of data due to type conversion";
100.     const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"};
101.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
102. 
103.     st_Data <st_Reg> Info_1;
104.     st_Data <st_Bio> Info_2;
105. 
106.     string  H[];
107. 
108.     StringSplit(T, ' ', H);
109.     for (uint c = 0; c < H.Size(); c++)
110.     {
111.         st_Reg  reg;
112.         st_Bio  bio;
113. 
114.         reg.SetValue(K[c], H[c]);
115.         bio.SetValue(K[c], M[c], H[c]);
116. 
117.         Info_1.Set(reg);
118.         Info_2.Set(bio);
119.     }
120. 
121.     PrintX(Info_1.Get(3).GetValue());
122.     PrintX(Info_1.Get(13).GetValue());
123.     CheckBio(Info_2);
124. }
125. //+------------------------------------------------------------------+

コード04

コード04はコード01と同じ効果を持ちます。しかしコード04では、構造体間で継承を使用しています。そのため、st_Base構造体に対して行ったあらゆる改善は、自動的にコード全体に反映されます。言い換えると、どんなに小さな変更であっても、st_Base構造体に加えられた変更は自動的にst_Regとst_Bioで利用可能になります。

もちろん、これは手続き、関数、または変数がコードのpublic部分に含まれている場合に限られます。つまり、publicインターフェースとして公開されているメンバーのみが対象です。もしst_Base内のメンバーがpublicでアクセス可能でない場合、st_Regやst_Bioはそれらを直接利用することはできません。

コード04で触れなかった重要な点として、15行目と30行目では継承がpublicで行われていることが挙げられます。これにより、st_Baseのpublicメンバーにアクセスできるst_Regおよびst_Bioの変数は、st_Base構造体のすべてのpublic要素にもアクセスできるようになります。一方で、もしこれらの行で予約語publicの代わりにprivateを使用していた場合、st_Bioやst_Regといった構造体を使う変数は、st_Baseの内容にアクセスできなくなります。理解を深めるために、付録のファイルを使って実際に試してみることを推奨します。

本記事の主なテーマは継承であるため、このテーマでもう少し遊んでみましょう。ただし、少し楽しい形で進めます。そのために新しいトピックへ移ります。難しい専門知識が必要な内容だと思う必要はありません。実際にはそうではなく、本連載を学んでいる皆さんなら誰でも同様のことを実現できます。

ただしそのためには、これまでの記事で扱ったいくつかの概念を適用する必要があります。では、楽しい話を続けましょう。ここからが本当の楽しみの始まりです。


構造、テンプレート、継承:騒々しい組み合わせ

多くの人はMQL5の能力を十分に理解していません。これは、MQL5がCやC++のすべての機能を持っているわけではないためです。しかし同時に、CやC++と比較すると、MQL5でのプログラミングははるかにシンプルです。もしこれらの記事がCやC++についての内容であったなら、多くの人は途中で挫折していたでしょう。しかしここではMQL5に限定して扱うため、内容の多くは非常にシンプルなものになります。ただし、その単純さゆえに十分に研究されておらず、結果としてほとんど神秘的なもののように扱われてしまっています。

ここで重要になるのが、これまで見てきた要素をすべて組み合わせることです。つまり、構造化プログラミングと、構造体、テンプレート、そして継承を一つのコード内で組み合わせることは可能なのでしょうか。可能です。そして、この内容は複雑に見えるかもしれませんが、私にとってはこれまで示してきた内容と比べてもわずかに複雑な程度にすぎません。

しかし、これまで扱ってきた概念をしっかりと学び、理解すれば、最終的にはこれらを組み合わせて非常に興味深いコードを書くことができるようになります。なぜなら、多くの人にとって大量のコードを書く必要がある処理を、私たちは非常にシンプルなコードで実現できるからです。そして、その多くの処理をコンパイラに任せることができます。

前のトピックで扱ったようなコードをいきなりすべての要素と組み合わせると混乱してしまうため、ここでは段階的に進めていきます。そうすることで、単なるコードの丸暗記ではなく、概念そのものの重要性を理解できるようになります。

コードは次のように始まります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+

コード05

コード05については理解できると思います。次の段階に進みましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. }
40. //+------------------------------------------------------------------+

コード06

素晴らしいです。これで継承のメカニズムを実装できました。現在、非常にシンプルで基本的な2つの構造体が存在しています。ここから次のステップに進みます。この段階では、「リスト」とみなすことができるものを実装していきます。これも非常にシンプルであり、以下に示す通りです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. };
40. //+------------------------------------------------------------------+
41. struct st_List
42. {
43.     private :
44.         st_Dev01    List[];
45.         uint        nElements;
46.     public  :
47.     //+----------------+
48.         void Clear(void)
49.         {
50.             ArrayFree(List);
51.             nElements = 0;
52.         }
53.     //+----------------+
54.         bool AddList(const st_Dev01 &arg)
55.         {
56.             nElements += (nElements == 0 ? 2 : 1);
57.             ArrayResize(List, nElements);
58.             List[nElements - 1] = arg;
59. 
60.             return true;
61. 
62.         }
63.     //+----------------+
64.         st_Dev01 SearchKey(const uint arg)
65.         {
66.             for (uint c = 1; c < nElements; c++)
67.                 if (List[c].GetKey() == arg)
68.                     return List[c];
69. 
70.             return List[0];
71.         }
72.     //+----------------+
73. };
74. //+------------------------------------------------------------------+

コード07

ご覧の通り、これまでおこなってきた内容と比べて何も変更していません。すべては依然としてシンプルで教育的なままです。ここでは、すべてが正しく動作していることを確認するために、短いデータリストを作成します。これは以下のコードで確認できます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     const string Names[] = 
11.         {
12.             "Daniel Jose",
13.             "Edimarcos Alcantra",
14.             "Carlos Almeida",
15.             "Yara Alves"
16.         };
17. 
18.     st_List list;
19. 
20.     for (uint c = 0; c < Names.Size(); c++)
21.     {
22.         st_Dev01 info;
23. 
24.         info.SetKeyInfo(c, Names[c]);
25.         list.AddList(info);
26.     }
27. 
28.     PrintX(list.SearchKey(2).GetInfo());
29. }
30. //+------------------------------------------------------------------+

コード08

ご注意ください。コード07のすべては実際にはヘッダーファイルでした。それはコード08の4行目でインクルードされています。10行目では、いくつかの名前を含む小さな配列を作成しました。18行目ではデータ構造体が示されています。25行目では構造体そのものを作成しました。28行目では、キーを使用して、その特定のキーに関連する情報を検索しています。その結果としてコードを実行すると、以下のような結果が得られます。

図05

要するに、特別なことは何もありません。これまでに示し、学習してきた内容と同様に、すべては期待通りに動作しています。しかし、ここからが新しい内容であり、本当の面白さが始まります。今回の新しい点は、このリストシステムにオーバーロードを導入することです。処理をできるだけスムーズに進めるために、後ろの行から前に向かって進めていきます。ヘッダーファイルのコードを修正し、以下のような形にします。なお、元のヘッダーファイルはコード07であったことを思い出してください。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. };
40. //+------------------------------------------------------------------+
41. template <typename T>
42. struct st_List
43. {
44.     private :
45.         T    List[];
46.         uint nElements;
47.     public  :
48.     //+----------------+
49.         void Clear(void)
50.         {
51.             ArrayFree(List);
52.             nElements = 0;
53.         }
54.     //+----------------+
55.         bool AddList(const T &arg)
56.         {
57.             nElements += (nElements == 0 ? 2 : 1);
58.             ArrayResize(List, nElements);
59.             List[nElements - 1] = arg;
60. 
61.             return true;
62. 
63.         }
64.     //+----------------+
65.         T SearchKey(const uint arg)
66.         {
67.             for (uint c = 1; c < nElements; c++)
68.                 if (List[c].GetKey() == arg)
69.                     return List[c];
70. 
71.             return List[0];
72.         }
73.     //+----------------+
74. };
75. //+------------------------------------------------------------------+

コード09

コード08が引き続き機能するためには、以下に示す形式に更新する必要があります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     const string Names[] = 
11.         {
12.             "Daniel Jose",
13.             "Edimarcos Alcantra",
14.             "Carlos Almeida",
15.             "Yara Alves"
16.         };
17. 
18.     st_List <st_Dev01> list;
19. 
20.     for (uint c = 0; c < Names.Size(); c++)
21.     {
22.         st_Dev01 info;
23.         
24.         info.SetKeyInfo(c, Names[c]);
25.         list.AddList(info);
26.     }
27. 
28.     PrintX(list.SearchKey(2).GetInfo());
29. }
30. //+------------------------------------------------------------------+

コード10

コード10では、元のコード08と比較して変更されたのは18行目のみであることに注意してください。

完璧です。これでテンプレート化されたリスト型が完成しました。このような状況はこれまでの記事でも示してきた通りであり、特に慌てる必要はありません。次のステップはやや複雑になります。しかし、これを段階的に説明する簡単な方法はないため、全体を一度に、あるいは2段階に分けて見る必要があります。まずヘッダーファイルに注目し、次にメインファイルを確認します。混乱しないようにしつつも、注意が逸れないようにしてください。

まずヘッダーファイルから始めます。これは以下のコードに示されています。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename KEY>
05. struct st_Base
06. {
07.     private :
08.         KEY KeyValue;
09.     public  :
10.     //+----------------+
11.         void SetKey(const KEY arg)
12.         {
13.             KeyValue = arg;
14.         }
15.     //+----------------+
16.         KEY GetKey(void)
17.         {
18.             return KeyValue;
19.         }
20.     //+----------------+
21. };
22. //+------------------------------------------------------------------+
23. template <typename KEY>
24. struct st_Dev01 : public st_Base <KEY>
25. {
26.     private :
27.         string ValueInfo;
28.     public  :
29.     //+----------------+
30.         void SetKeyInfo(KEY arg1, string arg2)
31.         {
32.             SetKey(arg1);
33.             ValueInfo = arg2;
34.         }
35.     //+----------------+
36.         string GetInfo(void)
37.         {
38.             return ValueInfo;
39.         }
40.     //+----------------+
41. };
42. //+------------------------------------------------------------------+
43. template <typename T, typename KEY>
44. struct st_List
45. {
46.     private :
47.         T    List[];
48.         uint nElements;
49.     public  :
50.     //+----------------+
51.         void Clear(void)
52.         {
53.             ArrayFree(List);
54.             nElements = 0;
55.         }
56.     //+----------------+
57.         bool AddList(const T &arg)
58.         {
59.             nElements += (nElements == 0 ? 2 : 1);
60.             ArrayResize(List, nElements);
61.             List[nElements - 1] = arg;
62. 
63.             return true;
64. 
65.         }
66.     //+----------------+
67.         T SearchKey(const KEY arg)
68.         {
69.             for (uint c = 1; c < nElements; c++)
70.                 if (List[c].GetKey() == arg)
71.                     return List[c];
72. 
73.             return List[0];
74.         }
75.     //+----------------+
76. };
77. //+------------------------------------------------------------------+

コード11

ヘッダーファイルのコード11で起きていることに注目し、これまで本記事で見てきた他のファイルと比較してください。コード11は以前のものよりもはるかに興味深い構造になっていることが分かるはずです。しかし、ここで慌てる必要はありません。少なくともここまで説明してきた範囲では、警戒すべき点はありません。私がCやC++を学んでいた数年前は、これを理解するのにすぐには至りませんでした。なぜなら、その仕組みを説明してくれる人を見つけるのが非常に困難だったからです。しかしここでは、読者の皆さんは必ず理解できるはずです。

注意してください。4行目ではコンパイラがオーバーロードを実行します。これは、構造体コードに対してテンプレートを定義しているために発生します。ご存知の通り、コンパイラは必要なデータ型ごとにバージョンを生成します。ここまでは最も単純な部分です。問題はここからで、少なくとも私の経験ではここが最も混乱する部分です。

st_Dev01構造体はst_Base構造体を継承していますが、st_Baseはテンプレートであるため、この構造体もテンプレートとして定義されなければなりません。つまり、どのように考えても、テンプレート構造体を継承する場合、継承側もテンプレート構造体として定義する必要があります。これはそれほど複雑ではなく、必要なのは23行目の定義だけです。

しかし、ここが最も難しい点ですが、この23行目で定義された型を基底構造体へ移動する必要があります。つまりst_Baseは後からコード内で定義されるこの型情報を受け取る必要があります。一見すると単純ですが、実際にはこれを理解するのに非常に時間がかかります。私は当時、コード09の22行目の宣言をコード11の24行目の宣言へ変更する必要があることを理解するまでに長い時間を要しました。

当時この点を説明してくれる人は誰もいなかったため、非常に苦労しました。おそらく皆さんは、まず23行目を定義し、その後30行目を定義するのが自然だと考えるでしょう。しかし、24行目の定義を正しくおこなわなければ、コードはまったくコンパイルされません

このリストは状況に応じてオーバーロードされるデータ構造を使用するため、st_List構造体の宣言も修正する必要があります。これは43行目を変更し、その後57行目を調整するだけで解決できます。これでヘッダーファイルはほぼ完成です。唯一まだ特定のデータ型に結びついているのは27行目の宣言ですが、これは後で変更します。それでは次に、メインファイルがどのようになったかを見ていきます。以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     #define def_TypeKey uint
11. 
12.     const string Names[] = 
13.         {
14.             "Daniel Jose",
15.             "Edimarcos Alcantra",
16.             "Carlos Almeida",
17.             "Yara Alves"
18.         };
19. 
20.     st_List <st_Dev01 <def_TypeKey>, def_TypeKey> list;
21. 
22.     for (uint c = 0; c < Names.Size(); c++)
23.     {
24.         st_Dev01 <def_TypeKey> info;
25. 
26.         info.SetKeyInfo(c, Names[c]);
27.         list.AddList(info);
28.     }
29. 
30.     PrintX(list.SearchKey(2).GetInfo());
31. 
32.     #undef def_TypeKey
33. }
34. //+------------------------------------------------------------------+

コード12

「ああ神よ!我々をお許しください!この男は正気ではない。よくもここに来て上のコードを見せられるものだ。初心者プログラマーのことを考えていないのか。そんな狂気のコードを見せて我々を殺すつもりか。」

落ち着いてください、読者の皆さん。取り乱さないでください。コード12は、一見かなり面白いにもかかわらず、ほとんど退屈に感じるほど単純です。しかし注意してください。コード内の複数の場所で同じ処理を繰り返す必要があるため、10行目でディレクティブを使用し、それらの箇所を迅速かつ安全に変更できる定義を作成しています。コード12では変更点は2つだけです。1つ目は20行目です。この変数の宣言をコード10における同じ変数の宣言と比較してください。

オーバーロードは今回作成しているデータ構造にも影響するため、24行目の宣言も変更する必要があります。ここではリストに格納される構造体を宣言しています。それ以外のコードはすべて変更されていません。実際、結果は図05で示されたものと同じになります。

しかし、このトピックはまだ終わりではありません。さらにもう一つ改善できる点があり、それを今から行います。これにより、ほぼ完璧な構造体リストを作成することができます。ただし「ほぼ」と言ったのは、小さな問題がまだ残っているためです。その問題はクラスを使用しない限り、つまりOOPを使わない限り解決できません。これは後ほど、その問題とOOPによる解決方法を示す際に明らかになります。

深呼吸してください。ここからさらに深い内容へと進みます。ヘッダーファイルに戻り、再び以下のように修正します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename KEY>
05. struct st_Base
06. {
07.     private :
08.         KEY KeyValue;
09.     public  :
10.     //+----------------+
11.         void SetKey(const KEY arg)
12.         {
13.             KeyValue = arg;
14.         }
15.     //+----------------+
16.         KEY GetKey(void)
17.         {
18.             return KeyValue;
19.         }
20.     //+----------------+
21. };
22. //+------------------------------------------------------------------+
23. template <typename KEY, typename INFO>
24. struct st_Dev01 : public st_Base <KEY>
25. {
26.     private :
27.         INFO ValueInfo;
28.     public  :
29.     //+----------------+
30.         void SetKeyInfo(KEY arg1, INFO arg2)
31.         {
32.             SetKey(arg1);
33.             ValueInfo = arg2;
34.         }
35.     //+----------------+
36.         INFO GetInfo(void)
37.         {
38.             return ValueInfo;
39.         }
40.     //+----------------+
41. };
42. //+------------------------------------------------------------------+
43. template <typename T, typename KEY>
44. struct st_List
45. {
46.     private :
47.         T    List[];
48.         uint nElements;
49.     public  :
50.     //+----------------+
51.         void Clear(void)
52.         {
53.             ArrayFree(List);
54.             nElements = 0;
55.         }
56.     //+----------------+
57.         bool AddList(const T &arg)
58.         {
59.             nElements += (nElements == 0 ? 2 : 1);
60.             ArrayResize(List, nElements);
61.             List[nElements - 1] = arg;
62. 
63.             return true;
64. 
65.         }
66.     //+----------------+
67.         T SearchKey(const KEY arg)
68.         {
69.             for (uint c = 1; c < nElements; c++)
70.                 if (List[c].GetKey() == arg)
71.                     return List[c];
72. 
73.             return List[0];
74.         }
75.     //+----------------+
76. };
77. //+------------------------------------------------------------------+

コード13

ご覧の通り、このコード13で変更したのは23行目だけです。それだけです。これで、キーとして任意のデータ型を使用できるようになり、さらにそのキーに関連付けられる情報についても任意のデータ型を使用できるようになりました。これを理解したうえで、メインコードがどのようになっているかを見ることができます。以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     #define def_TypeKey     string
11.     #define def_TypeInfo    string
12. 
13.     const string Names[][2] = 
14.         {
15.             "Daniel Jose"       , "Chief Programmer",
16.             "Edimarcos Alcantra", "Programmer",
17.             "Carlos Almeida"    , "Junior Programmer",
18.             "Yara Alves"        , "Accounting"
19.         };
20. 
21.     st_List <st_Dev01 <def_TypeKey, def_TypeInfo>, def_TypeKey> list;
22. 
23.     for (int c = 0; c < ArrayRange(Names, 0); c++)
24.     {
25.         st_Dev01 <def_TypeKey, def_TypeInfo> info;
26. 
27.         info.SetKeyInfo(Names[c][1], Names[c][0]);
28.         list.AddList(info);
29.     }
30. 
31.     PrintX(list.SearchKey("Chief Programmer").GetInfo());
32.     PrintX(list.SearchKey("Guest").GetInfo());
33.     
34.     #undef def_TypeKey
35.     #undef def_TypeInfo
36. }
37. //+------------------------------------------------------------------+

コード14

ご注意ください。このコードでは、変更されたのはほとんどありません。しかし、このわずかな違いだけで、実用面および機能面の両方において非常に興味深い結果を得ることができる、かなり単純なコードになります。変更点が非常に小さいため、コードの動作を理解する機会を設けます。いずれにしても結果は以下の通りです。

図06

素晴らしいです。図06はまさに圧巻です。一見複雑に見えるものが、記事で説明した内容を積み重ねていくことで、驚くほどシンプルになる様子がよく分かります。


最後に

間違いなく、本記事の内容を理解するにはある程度の時間が必要になるでしょう。ここで示されている内容は一見するとオブジェクト指向的に見えますが、多くの人は「OOPとして説明される内容」が、実際には構造化プログラミングの原理に基づいて実現されているとは考えていません。

前述の通り、OOPを説明する前に、その基盤となった考え方を理解する必要があります。なぜなら、OOPは構造化プログラミングの限界を解決するために生まれたものだからです。しかし実際には、OOPの専有物と考えられている多くの処理は、構造化プログラミングの段階でも実現されています。

したがって、これらの記事で示された各項目を丁寧に学習してください。付録のコードを使い、実践と学習を繰り返しながら、正しいプログラミングの理解を深めてください。そして次回の記事では、さらに面白い内容が続きます。またお会いしましょう。

MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15922

添付されたファイル |
Anexo.zip (2.52 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5における取引へのコンピュータビジョンの統合(第2回):アーキテクチャを2D RGB画像解析に拡張する MQL5における取引へのコンピュータビジョンの統合(第2回):アーキテクチャを2D RGB画像解析に拡張する
取引におけるコンピュータビジョン:仕組みと開発手順本記事では、RGB画像として価格チャートを認識するアルゴリズムを構築し、アテンション機構と双方向LSTM層を用いる方法について説明します。結果として、EURUSDの価格を予測する動作モデルを構築し、検証セクションにおいて最大55%の正解率を得ます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
ラクダアルゴリズム(CA) ラクダアルゴリズム(CA)
ラクダアルゴリズムは2016年に開発され、砂漠におけるラクダの行動をシミュレートして最適化問題を解く手法です。本アルゴリズムは、温度、補給、持久力といった要素を考慮しています。また、本記事では改良版であるCAmも紹介しており、ガウス分布による解生成とオアシス効果パラメータの最適化という主要な改良が含まれています。