【超入門】MQL5 EA講座 第53回「クラスについて6 -仮想関数-」【EAの作り方】

MQL5でEA作ろう講座

前回は コンストラクタ について解説しました。

改めて前回の内容をおさらいをしておくと、

インスタンスクラス構造体Enum列挙型を実際に使う時に宣言する、変数名のようなものです。

ということをお伝えしました。

インスタンスについては第54回「インスタンスについて」をご参照ください。

クラスについては第48回「クラスについて1-クラスとは?-」をご参照ください。

構造体については第22回「構造体」をご参照ください。

Enum列挙型については第21回「Enum列挙型」をご参照ください。

関数については第25回「関数について」をご参照ください。

変数については第9回「変数について」をご参照ください。

データ型については第10回「データ型」をご参照ください。

今回は 仮想関数 というものについてお話ししたいと思います。

スポンサーリンク
スポンサーリンク

仮想関数とは?

仮想関数は、派生クラスに関連する概念になります。

※「派生クラスってなんだったっけ?」という方は↓

MQL5 EA講座 第51回「クラスについて4 -派生クラス-」 をご覧ください

仮想関数は、例えば

といった希望を実現させるための関数です。

物事には、共通点があって相違点があります。

それはデータ処理においても同様であり、データ処理における、共通点部分と相違点部分を切り分けして、共通規格化したインターフェイスが仮想関数と言えます。

全ての機械の共通部分だけ設計して、機械ごとの細かい違いはその機械ごとに別途設計する、といった感じ・・・

仮想関数の作り方

仮想関数の作り方ですが、派生元のクラスと派生先のクラスでそれぞれ記述のルールがあります。

※以降、派生元のクラス親クラス、派生先のクラス子クラスと呼称したいと思います。

親クラス子クラスそれぞれの記述ルールを順を追って見ていきましょう。

親クラス側の記述

まずは、親クラスの記述から。

親クラスではまず、仮想関数を作る宣言をしなくてはいけません。仮想関数を作る宣言をする場合は、

virtual

というキーワードを冒頭に記述します。

//親クラスの宣言
class parentClass
{
  
  public:
  int publicInteger;
  //親クラスで、仮想関数「virtualFunction」を宣言
  virtual int virtualFunction(){return publicInteger;}

};

↑のサンプルコードのように

virtualキーワード→データ型関数

という順で記述していくことによって、仮想関数を作る、という宣言になります。

関数の処理実装記述に関する文法的なルールは、普通のメンバ関数と同じです。

ただし、詳細な処理記述は各子クラスで行う前提の場合が多いので、親クラスでの記述はインライン定義を使う傾向が多いかもしれません。

インライン定義とは、メンバ関数の処理実装記述をグローバル領域ではなく、クラス内の{}内で行うことです。

メンバというのはクラス内で定義された個々の変数関数のことです。

子クラス側の記述

続いて、子クラス側の記述です。

仮想関数というのは、親クラスと連動した概念なので、先述した親クラス側の記述を受けて、子クラス関数仮想関数としての属性を実装させていくことになります。

子クラス側で、仮想関数の宣言をする際は、関数名、戻り値、引数を一致させる

子クラス側で、仮想関数の宣言をする際は、関数名、戻り値引数を一致させる必要があります。

言い換えれば、関数名、戻り値引数を一致させていれば、それ以外の処理実装記述は変更しても構わない、ということになります。

//親クラスの宣言
class parentClass
{
  
   public:
  int publicInteger;
  //親クラスで、仮想関数「virtualFunction」を宣言
  virtual int virtualFunction(){return publicInteger;}

};

//子クラスの宣言
class childClass:public parentClass
{
   public:
  //子クラスで、仮想関数「virtualFunction」を宣言
  int virtualFunction();
   
};
//仮想関数の処理実装記述
 int childClass::virtualFunction()
 {
   publicInteger=3+5;
   return publicInteger;
 }

//子インスタンスの宣言
childClass child;

void OnStart()
{
 //仮想関数を呼び出し、その値をログ出力 
  Print(child.virtualFunction());
    
}

parentClassを継承した派生クラスchildClassをまず作りました。

続いて、childClassの{}内にvirtualFunctionの記述を行います。

なおこの時、virtualキーワードをつけてもつけなくてもコンパイルエラーにはなりません。

childClassからさらに派生クラスをつくり、そこでvirtualFunctionを仮想関数として機能させるのであればvirtualキーワードはつけるべきですし、childClassからもう派生させる予定がないのであれば、virtualキーワードは不要・・・ということになります。

parentClassにおけるvirtualFunctionの処理記述は

{return publicInteger;}

だけ、というシンプルなものでしたが、

childClassでは処理実装記述をもう少し付け加えようと思います。従って、インライン定義ではなく、グローバル領域での定義にしています。

※グローバル領域とは関数の外の領域のことです。

//仮想関数の処理実装記述
 int childClass::virtualFunction()
 {
   publicInteger=3+5;
   return publicInteger;
 }

メンバ変数「publicInteger」に3+5の計算結果を代入し、それを戻り値として返す仕組みにしています。

戻り値とは、関数が処理を行った「結果」として出力される値のことです。

これによって、子クラスchildClassから呼び出すvirtualFunctionは、parentClassのvirtualFunctionの機能を上書き(オーバーライド)し、3+5の計算結果を格納したメンバ変数「publicInteger」を戻り値として返す関数になりました。

メンバ変数「publicInteger」はparentClassから継承されているメンバなので、childClassの{}内では宣言していませんが、使えるようになっています。

↓の動画をご覧ください。

最初に、子クラスのvirtualFunctionをログ出力させ、途中で親クラスのvirtualFunctionを呼び出す記述に変更して再コンパイルした動画です。

子クラスでのvirtualFunctionは3+5の計算結果をメンバ変数「publicInteger」に返す関数なので8がログ出力親クラスでのvirtualFunctionはpublicIntegerを返す記述しかしていないので、0がログ出力されているのがお分かりいただけるかと思います。

仮想関数の使用例

仮想関数の使い方についてのサンプルコードをもう一つ見てみましょう。↓

//親クラスの宣言
class parentClass
{
  
  public:
  int publicInteger;
  //親クラスで、仮想関数「virtualFunction」を宣言
  virtual void virtualFunction(){Print("親クラスの処理記述");}

};

//子クラスの宣言
class childClass:public parentClass
{
   public:
  //子クラスで、仮想関数「virtualFunction」を宣言
  void virtualFunction(){Print("子クラスの処理記述");}
   
};

childClass child;
void OnStart()
{
 
    child.virtualFunction();
}

↓の動画は、サンプルコードを実際に実行したものです。

最初は親クラス継承メンバとして呼び出し、実行しました。

途中でコメントアウトしていた仮想関数部分をコメントアウト解除して、ファイルを実行しました。

出力ログの内容が、

親クラスの処理記述”→”子クラスの処理記述”

に変わっているのがわかるかと思います。

おまけ 

その1-仮想関数の戻り値を親クラスと異なったデータ型で設定しようとした場合-

先述した通り、派生クラスでの仮想関数実装時のルールとして関数名、引数戻り値を同じにしなくてはいけません。

例えば、戻り値が異なっている場合、

「’仮想関数名’ – overriding virtual function with different return type(異なるデータ型戻り値仮想関数を上書きしようとしています)」

というコンパイルエラーが発生します

//親クラスの宣言
class parentClass
{
  
  public:
  int publicInteger;
  //親クラスで、仮想関数「virtualFunction」を宣言
  virtual int virtualFunction(){return publicInteger;}

};

//子クラスの宣言
class childClass:public parentClass
{
   public:
  //子クラスで、仮想関数「virtualFunction」を宣言
  double virtualFunction(){return publicInteger;};
//親クラスでは仮想関数の戻り値をint型にしているので、エラーになる。
   
};

-関数のオーバーライド-

同じ関数名に複数の異なる役割を担わせることを

多態性(ポリモーフィズム Polymorphism)

と言います。

同じ関数名に複数の異なる役割を担わせること、といえば↓

MQL5 EA講座 第45回「関数のオーバーロード」

で解説したオーバーロード関数も、まさに「同じ関数名に複数の異なる役割を担わせる処理」でしたね?

一方で、今回紹介した仮想関数のような仕組みを関数オーバーライドと言います。

オーバーロードオーバーライド、似たような名前の響きですが、違いは何でしょうか?

オーバーロード→「多重定義」 すなわち複数ある関数書式のうち、付与した引数によって、関数の書式が選択される。↓

//オーバーロードとは?

//多重定義1
void overLoad(){}
//多重定義2
void overLoad(int x){}
//多重定義3
void overLoad(int x,double y){}
void OnStart()
{
 //引数2つの多重定義3が書式として採用されている
   overLoad(1,2.0);
}

オーバーライド→「上書き」つまり、呼び出すクラスによって、関数の書式が選択される。

すなわち、親クラス子クラスのような、継承関係にあるクラスにおいて、親クラス関数子クラスで定義しなおす事

というのが、違いになります。↓

//オーバーライドとは?

//子クラスインスタンスの宣言
childClass child;
//親クラスインスタンスの宣言
parentClass parent;

void OnStart()
{
//子クラスインスタンスからvirtualFunctionを呼び出しているので、子クラスの書式が適用されている
  child.virtualFunction();
    
}

まとめ

今回は 仮想関数 について解説しました。

今回の記事では以下のことを学びました

仮想関数の具体的な使い方に関しては↓

MQL5 EA講座 第109回「インジケータの値を簡単に取得できるクラスを作る」

MQL5 EA講座 第110回「インジケータ取得クラスに派生クラスを追加する」

の中で改めて解説しておりますので、そちらも参考にしていただければと思います。

今回は以上になります。

最後までお読みいただきありがとうございました<m(__)m>

【超入門】MQL5 EA講座 第52回「クラスについて5 -コンストラクタ-」【EAの作り方】

       →【超入門】MQL5 EA講座 第54回「インスタンスについて」【EAの作り方】

クラスの実際の使用例に関しては、今後の講座記事にはなりますが、以下の記事で解説していますので、今はよくわからなくてもご安心ください。

MQL5 EA講座 第71回「トレード用のオリジナルクラスを作る」

MQL5 EA講座 第82回「ポジション情報管理クラスを作る-その1」

MQL5 EA講座 第83回「ポジション情報管理クラスを作る-その2」

MQL5 EA講座 第88回「待機注文情報取得用のクラスを追加する」

MQL5 EA講座 第96回「トレーリングストップクラスを作る1」

MQL5 EA講座 第97回「トレーリングストップクラスを作る2」

MQL5 EA講座 第100回「ブレイクイーブンストップ関数を作る」

コメント

タイトルとURLをコピーしました