改めて前回の内容をおさらいをしておくと、
※インスタンス→クラスや構造体、Enum列挙型を実際に使う時に宣言する、変数名のようなものです。
- コンストラクタもメンバ関数なので、他のメンバ関数と同様に、クラスの{}内に関数宣言をするのは同じだが、コンストラクタのデータ型は常にvoid型 なので、コンストラクタを宣言する際にデータ型の指定は不要。
- 宣言時に、関数名をクラスの名前と完全に同じにすることによって、そのメンバ関数はコンストラクタと認識される。
- コンストラクタのアクセスレベルはpublicに限定される。
ということをお伝えしました。
※インスタンスについては第54回「インスタンスについて」をご参照ください。
※クラスについては第48回「クラスについて1-クラスとは?-」をご参照ください。
※Enum列挙型については第21回「Enum列挙型」をご参照ください。
※関数については第25回「関数について」をご参照ください。
※変数については第9回「変数について」をご参照ください。
※データ型については第10回「データ型」をご参照ください。
今回は 仮想関数 というものについてお話ししたいと思います。
仮想関数とは?
※「派生クラスってなんだったっけ?」という方は↓
MQL5 EA講座 第51回「クラスについて4 -派生クラス-」 をご覧ください
仮想関数は、例えば
といった希望を実現させるための関数です。
物事には、共通点があって相違点があります。
それはデータ処理においても同様であり、データ処理における、共通点部分と相違点部分を切り分けして、共通規格化したインターフェイスが仮想関数と言えます。
仮想関数の作り方
仮想関数の作り方ですが、派生元のクラスと派生先のクラスでそれぞれ記述のルールがあります。
※以降、派生元のクラスを親クラス、派生先のクラスを子クラスと呼称したいと思います。
親クラスと子クラスそれぞれの記述ルールを順を追って見ていきましょう。
親クラス側の記述
まずは、親クラスの記述から。
親クラスではまず、仮想関数を作る宣言をしなくてはいけません。仮想関数を作る宣言をする場合は、
virtual
というキーワードを冒頭に記述します。
//親クラスの宣言
class parentClass
{
public:
int publicInteger;
//親クラスで、仮想関数「virtualFunction」を宣言
virtual int virtualFunction(){return publicInteger;}
};
↑のサンプルコードのように
という順で記述していくことによって、仮想関数を作る、という宣言になります。
関数の処理実装記述に関する文法的なルールは、普通のメンバ関数と同じです。
ただし、詳細な処理記述は各子クラスで行う前提の場合が多いので、親クラスでの記述はインライン定義を使う傾向が多いかもしれません。
※インライン定義とは、メンバ関数の処理実装記述をグローバル領域ではなく、クラス内の{}内で行うことです。
※メンバというのはクラス内で定義された個々の変数や関数のことです。
子クラス側の記述
続いて、子クラス側の記述です。
仮想関数というのは、親クラスと連動した概念なので、先述した親クラス側の記述を受けて、子クラスの関数に仮想関数としての属性を実装させていくことになります。
子クラス側で、仮想関数の宣言をする際は、関数名、戻り値、引数を一致させる
子クラス側で、仮想関数の宣言をする際は、関数名、戻り値、引数を一致させる必要があります。
言い換えれば、関数名、戻り値、引数を一致させていれば、それ以外の処理実装記述は変更しても構わない、ということになります。
//親クラスの宣言
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)
と言います。
同じ関数名に複数の異なる役割を担わせること、といえば↓
で解説したオーバーロード関数も、まさに「同じ関数名に複数の異なる役割を担わせる処理」でしたね?
一方で、今回紹介した仮想関数のような仕組みを関数のオーバーライドと言います。
オーバーロードとオーバーライド、似たような名前の響きですが、違いは何でしょうか?
オーバーロード→「多重定義」 すなわち複数ある関数書式のうち、付与した引数によって、関数の書式が選択される。↓
//オーバーロードとは?
//多重定義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回「インジケータ取得クラスに派生クラスを追加する」
の中で改めて解説しておりますので、そちらも参考にしていただければと思います。
→【超入門】MQL5 EA講座 第54回「インスタンスについて」【EAの作り方】
※クラスの実際の使用例に関しては、今後の講座記事にはなりますが、以下の記事で解説していますので、今はよくわからなくてもご安心ください。
・MQL5 EA講座 第71回「トレード用のオリジナルクラスを作る」
・MQL5 EA講座 第82回「ポジション情報管理クラスを作る-その1」
・MQL5 EA講座 第83回「ポジション情報管理クラスを作る-その2」
・MQL5 EA講座 第88回「待機注文情報取得用のクラスを追加する」
・MQL5 EA講座 第96回「トレーリングストップクラスを作る1」
コメント