オブジェクト指向プログラミング
オブジェクト指向プログラミング(OOP)は、データと動作が密接に繋がっていながら、主に、データに焦点を当てたプログラミングです。オブジェクトがクラスインスタンスでありながら、クラスはデータと操作演算の両方によって構成されます。
オブジェクト指向のアプローチのコンポーネントは次の通りです。
- 型の封裝と拡張
- 継承
- ポリモーフィズム
- 多重定義(オーバーロード)
- 仮想関数
OOP は演算を操作演算のモデル化とみなします。モデル化されているのは、計算抽象化によって表されるオブジェクトです。よく知られているゲーム「テトリス」を書きたいとしましょう。これを行うためには、4 つの正方形で構成されて辺で接合されたランダムな図形をモデル化する方法を学ぶ必要があります。また図形の落下速度を調整し、図形の回転と移動の動作を定義する必要があります。画面上の図形の移動は井戸の境界によって限られており、この要件もモデル化されなければなりません。それに加えて、正方形のそろった行を破棄して得点を数える必要があります。
このように、この理解しやすいゲームは、図形モデル、井戸モデル、図形運動モデルといったいくつかのモデル作成を要します。これらの全てのモデルは抽象で、コンピュータでの計算で示されます。これらのモデルを説明するためには抽象データ型、ADT(またはコンプレックスデータ型)の概念が使用されます。厳密に言うと、DOM 内の「図形」運動のモデルはデータ型ではなく、「井戸」データ型の制限を使用した「図形」のデータ型に対する操作のセットです。
オブジェクトは クラス 変数です。オブジェクト指向プログラミングは、ADTの作成と使用を簡単にします。オブジェクト指向プログラミングは、継承メカニズムを使用します。継承の利点は、すでにユーザによって定義されたデータ型から派生型を得ることが可能になることです。
例えば、テトリス図形を作成するには、まず Shape 基本クラスを作成すると便利です。全ての 7 つの可能な図形の型を表現する他のクラスは、その基礎の上に作られます。図形の動作は基本クラスで定義され、各別個の図形の動作は派生クラスで実装されます。
OOPでは、オブジェクト自身がそれぞれの動作に責任を持ちます。ADTの開発者は、対応するオブジェクトから通常予測される動作を説明するためのコードを含むべきです。オブジェクト自体がその動作に責任を持つという事実は、このオブジェクトのプログラミング作業を非常に簡素化します。
画面上に図形を描画したい場合、どこが中心になってそれをどのように描画するかを知る必要があります。別々の図形がそれ自身を描画する方法を知っていれば、プログラマはこのような図形を使用した場合、「draw」メッセージを送信出来ます。
MQL5 言語は C++ に似ており ADT 実施のためにカプセル化メカニズムを備えています。カプセル化は特定の型の実装の内部の詳細を結合する一方、この型のオブジェクトに影響を与えることが出来る外部からアクセス可能の関数を組み合わせます。実装の詳細はこの型を使用するプログラムのからはアクセス出来ません。
OOP の概念は次のような関連する概念のセットで成り立ちます。
- 現実世界アクションのシミュレーション
- ユーザ定義のデータ型
- 実装の詳細の隠蔽
- 継承によるコード再利用の可能性
- 実行時の関数呼び出しの解釈
これらの概念のいくつかはかなり曖昧、いくつかは抽象的でいくつかは一般的です。
型の封裝と拡張
OOP はソフトウェアを書くのにバランスのとれたアプローチです。データと操作演算は一体化されています。このカプセル化は、ユーザ定義のデータ型を作成し、言語データ型を拡張しそれらと相呼応します。型の拡張は言語に使いやすいユーザ定義のデータ型、ならびに基本型を追加する機会です。
例えば抽象データ型の文字列は、理想的なよく知られた操作演算の種類を描画します。
文字列のユーザは、連結やプリントなどの文字列操作が特定の動作を持っていることを知っています。このような操作はメソッドと呼ばれます。
ADT の特定の実装は、例えば、文字列の長さの制限などを有することがあります。これらの制限は全ての操作演算に影響を与えます。同時に、内部またはプライベート実装の詳細はユーザがオブジェクトを見る方法には直接影響しません。例えば、文字列は多くの場合配列として実装されていて、配列の内部ベースアドレスや名称はユーザには必要でありません。
カプセル化はユーザ定義型へのオープンインタフェースが提供された時に実装の詳細を隠す機能です。MQL5 言語ではクラスと構造体の定義(classと struct )は private、protected 及び public のアクセスキーワードとともにカプセル化の規定に使用されています。
public キーワードは、キーワードの使用されたメンバへのアクセスが制限なくオープンなことを示します。このキーワードが指定されなかった場合、クラスメンバは、デフォルトでロックされています。private メンバは、そのクラスのメンバ関数のみからアクセス出来ます。
Protected クラス関数はこのクラスでだけでなくその継承クラスからも利用可能です。Public クラス関数は、クラス宣言の有効範囲内の全ての関数から利用可能です。この様な保護は、クラスの実装の一部を隠してデータの構造の予期しない変更を防止します。アクセス制限やデータ隠蔽はオブジェクト指向プログラミングの特徴です。
通常、クラス関数は保護されており protected 修飾子を使用して宣言されます。値の読み書きは public 修飾子を用いて定義された特殊ないわゆるセットメソッドとゲットメソッドを用いて行われます。
例:
class CPerson |
このアプローチにはいくつかの利点があります。まず、関数名で関数の機能がわかります。つまりクラスメンバの値をセットまたはゲットするものです。次に、将来的に CPerson クラスまたはその派生クラスのいずれかで m_name 変数の型を変更する必要があるかもしれません。
この場合必要なのは SetName() と GetName() 関数の実装の変更だけです。 m_name のデータ型の変更はユーザに知られる余地もなく、CPerson クラスのオブジェクトはコード変更なしでプログラムで使用できます。
例:
struct Name class CPerson void CPerson::SetName(string n) string CPerson::GetFirstName(string full_name) string CPerson::GetLastName(string full_name) |
継承
継承によるコードの再利用の促進は OOP の特徴です。新しいクラスは、基本クラスと呼ばれる既存するクラスから作られます。派生クラスは基本クラスのメンバを使用するだけでなくそれらを変更したり補完したりすることが出来ます。
多くの型は既存の型のバリエーションです。多くの場合、各バリエーションのために新しいコードを開発することは面倒です。また、新しいコードは新しいエラーを意味します。派生クラスは基本クラスの記述を継承するのでコードの再開発と再テストは不要です。継承関係は階層構造を持ちます。
階層構造は、要素の全ての多様性と複雑さを複製することを可能にします。それはオブジェクトの分類を紹介します。例えば、元素表はガスを含みます。ガスは全ての元素に固有の特性を保有します。
不活性ガスは次に重要なサブクラスを構成します。ここでみられる階層は、アルゴン等の不活性ガスがガスで、ガスはひいてはシステムの一部であるということです。このような階層を使用すれば、不活性ガスの挙動が簡単に解釈出来ます。不活性ガスは他の全ての要素と同様に原子に陽子と電子があることがわかります。
不活性ガスは全てのガスのように常温で気体状態であることもわかります。不活性ガスのサブクラスに属するガスは全ての不活性ガスの特性であるように他の元素との通常の化学反応に入らないこともわかります。
幾何学的図形の継承の例を考えてみましょう。数々の単純な図形を記述するのに(円形、三角形、四角形、正方形等)一番良い方法は、全ての派生クラスの祖先である基本クラスを作成することです( ADT )。
図形を記述する最も一般的なメンバを含む基本クラス CShape を作成してみましょう。これらのメンバは図形の特徴であるプロパティ(図形の種類とメインアンカーポイントの座標)を記述します。
例:
//— 基本クラス Shape |
次に、基本クラスから派生した新しいクラスを作成し、それぞれのクラスを指定するのに必要なフィールドを追加します。円形の場合には、半径値が含まれているメンバを追加する必要があります。正方形は側面値によって特徴付けられます。従って、基本クラスの CShape から継承された派生クラスは次のように宣言されます。
//— 派生クラス circle public: |
正方形クラスの宣言も似ています。
//— 派生クラス Square public: |
オブジェクト作成時に基本クラスのコンストラクタが最初に呼び出されその後で派生クラスのコンストラクタが呼び出されることには留意するべきです。オブジェクト破壊時には派生クラスのデストラクタが呼び出されてから基本クラスのデストラクタ が呼び出されます。
従って、最も一般的なメンバを基本クラスの中で宣言することによって、派生クラスに特定のクラスを指定するメンバを追加することが出来ます。継承は何度も再利用することが出来る力強いコードライブラリの作成を可能にします。
既存クラスから派生クラスを作成するための構文は次の通りです。
class クラス名 : |
派生クラスの側面の 1 つは、そのメンバの後継者の視認性(オープンネス)です。public、protected 及び private キーワードは 基本クラスのメンバが派生クラスにどの程度使用可能かを示すものです。派生クラスヘッダーのコロンの後の public キーワードは、基本クラス CShape の protected 及び public メンバが派生クラス CCircle の protected 及び public メンバとして継承される必要があることを示します。
基本クラスの private クラスメンバは、派生クラスでは使用出来ません。public 継承は、派生クラス( CCircle と CSquare )が CShapes であることを意味します。つまり、Square( CSquare )は図形( CShape )ですが、その図形は必ずしも正方形である必要はありません。
派生クラスは基本クラスのバージョン例で基本クラスの protected と public メンバを継承します。基本クラスのコンストラクタとデストラクタは継承出来ません。基本クラスのメンバに加えて、派生クラスには新しいメンバが追加されます。
派生クラスは、基本クラスとは違うメンバ関数の実装を含むことが出来ます。これは、同名の関数の意味がシグネチャの違いによって異なるものになるオーバーロードとは全く違うものです。
protected 継承では、基本クラスの public 及び protected メンバは派生クラスの protected メンバになります。private 継承では、基本クラスの public 及び protected メンバは派生クラスの private メンバになります。
protected 及び private 継承では「派生クラスのオブジェクトは基本クラスのオブジェクトである」という関係は当てはまりません。protected 及び private 継承タイプはまれであり慎重に使用される必要があります。
継承の種類( public、protected 及び private )は派生クラスが基本クラスメンバにアクセスする方法に影響を及ぼさないことは留意されるべきです。全ての継承のタイプで、public 及び protected のアクセス指定とともに宣言された基本クラスのメンバのみが派生クラスによって利用出来ます。次の例を見てみましょう。
#property copyright “Copyright 2011, MetaQuotes Software Corp.” }; |
上記の例では CBaseClass の唯一の public メソッドはコンストラクタです。コンストラクタはクラスオブジェクトを作成する時に自動的に呼び出されます。従って、private メンバ m_member と protected メソッド Member() は外から呼び出すことは出来ません。しかし public 継承では、基本クラスの Member() メソッドは派生クラスから利用出来るようになります。
protected 継承では、基本クラスの public 及び protected メンバは派生クラスの protected メンバになります。これは、もし基本クラスの public データメンバとメソッドが外からアクセス出来たとすると、protected 継承ではそれらは派生クラスまた派生クラスの派生クラスなどによってのみ利用可能なことを意味します。
//+——————————————————————+ //— 基本クラスの変数を宣言し Pi 定数を 10 に設定してみる |
この例は基本クラス CBaseMathClass の SetPI() 及び GetPi() メソッドがプログラムのどこからでも呼び出せることを示します。 しかし同時に、それらから派生する CProtectedChildClass では、これらのメソッドは CProtectedChildClass クラスまたはその派生クラスのメソッドのみからしか呼び出せません。
private 継承では基本クラスの public 及びprotected メンバは派生クラスの private メンバになり、それ以上の継承を通してそれらを呼び出すことは不可能です。
MQL5 言語は多重継承を持っていません。
ポリモーフィズム
ポリモーフィズムは継承によって繋がったオブジェクトクラスが同じ関数要素を呼び出す時に使用されます。これは基本クラスだけでなくその子孫クラスの振る舞いも記述する普遍的なメカニズムを作成するのに役立ちます。
図形の面積を計算するために設計された基本クラス CShape を開発しメンバ関数 GetArea() を定義していきましょう。基本クラスからの継承によって生成された子孫クラスの全てで、特定の図形の面積を計算する規則に従って、この関数を定義し直します。
正方形( CSquare クラス)では面積がその辺を介して計算され、円( CCircle クラス)では面積が半径で表現されます。基本クラスと全ての子孫クラスの CShape 型のオブジェクトを格納出来る配列を作成することが出来ます。 更に配列の各要素に同じ関数を呼び出すことが出来ます。
例:
//— 基本クラス |
これで、派生クラスの全てがゼロの値を返すメンバ関数 getArea() を持つことになります。この関数の実装は各子孫関数内で異なります。
//— 派生クラス circle public: |
Square クラスの宣言も同じです。
//— 派生クラス Square public: |
正方形と円の面積を計算するためにはそれぞれ m_radius と m_square_side の値が必要となるので、それぞれのクラス宣言に関数 SetRadius() と SetSide() を追加します。
1 つの基本型 CShape から派生した異なる種類のオブジェクト( CCircle と CSquare )が両方 1 つのプログラムで使用されていると仮定します。ポリモーフィズムによって、基本クラス CShape オブジェクトの配列が作成出来ますが、配列の宣言時にはこれらのオブジェクトはまだ未知で型も定義されていません。
配列の各要素に含まれるオブジェクト型はプログラムの実行中に直接決定されます。これは関係するクラスオブジェクトの動的作成を必要とするのでオブジェクトのかわりにオブジェクトポインタを使用することが必要となります。
new 演算子はオブジェクトの動的作成に使用されます。このようなオブジェクトは、個別に明示的に delete 演算子を使用して削除される必要があります。そこで、CShape 型のポインタの配列を宣言し、次のスクリプトの例に示すように各要素に適切な型のオブジェクトを作成します( new クラス名 )。
//+——————————————————————+ //— 配列に派生オブジェクトを書き込む //— CCircle オブジェクトをあと 1 つ作成し shapes[1] にそのポインタを書き込む //— 意図的に shapes[2] の値を設定するのを忘れる //— 使用されない要素は NULL に設定する //— CSquare オブジェクトを作成し shapes[3] にそのポインタを書き込む //— CSquare オブジェクトを作成し shapes[4] にそのポインタを書き込む //— ポインタ配列ができたのでサイズを取得する //— 作成した全ての動的オブジェクトは削除が必要 |
delete 演算子を使用してオブジェクトを削除する時にはそのポインタの種類のチェックが必要なのでご注意ください。POINTER_DYNAMIC ポインタを持つオブジェクトのみが delete を使用して削除することが出来ます。他のポインタ型の場合はエラーが返されます。
しかし、ポリモーフィズムの定義には、継承時の関数の再定義に加えて、クラス内での 1 つの関数のパラメータで異なる複数の実装が含まれます。これはクラス内に同名で型及び/またパラメータの異なる関数がいくつか存在出来ることを意味します。この場合、ポリモーフィズムは関数オーバーロードを通して実装されます。
多重定義(オーバーロード)
1 つのクラス内で同名であるがパラメータの数が異なる複数のメソッドを定義することは可能です。この場合、メソッドはオーバーロードされたといわれ、その過程は、メソッドオーバーロードと呼ばれています。
メソッドオーバーロードはポリモルフィズム実現の方法の 1 つです。メソッドのオーバーロードは関数のオーバーロードと同じルールに従って行われます。
呼び出された関数が正確に一致していない場合、コンパイラは順次 3 つのレベルで適切な関数を検索します。
- 自己のクラスメソッド内での検索
- 自己に最も近い先祖から基本クラスに順にさかのぼって基本クラスのメソッドを検索
- 他の関数内での検索
もし全てのレベルで正確な一致が 1 つもなく、異なるレベルでいくつかの適切な関数がある場合は、最低レベルでの関数が使用されます。各レベル内で、複数の適切な関数が存在することはありません。
仮想関数
virtual キーワードは、実行時に動的に基本クラスと派生クラスの関数から適切な関数メンバを選択するメカニズムを提供する関数指定子です。構造体は仮想関数を含むことが出来ません。virtual キーワードは、関数メンバのみの宣言を変更することが出来ます。
仮想関数は通常の関数同様に実行可能な本体を持たなければなりません。呼び出される際にはそのセマンティックは他の関数の物と同様です。
仮想関数は派生クラスでオーバーライドされる場合があります。仮想関数に対してどの関数定義が呼び出されるかは(実行時に)動的に選択されます。基本クラスに仮想関数が含まれ、派生クラスがこの関数の独自のバージョンを持っているのが典型的なケースです。
基本クラスへのポインタは、基本クラスオブジェクトまたは派生クラスのオブジェクトのいずれかを示すことが出来ます。呼び出されるメンバ関数の選択は実行時に行われ、ポインタの型ではなくオブジェクトの型に依存します。派生型のメンバが存在しない場合は、基本クラスの仮想関数がデフォルトで使用されます。
デストラクタ は virtual キーワードで宣言されたかされなかったにかかわらずは常に仮想です。
MT5_Tetris.mq5 の例で仮想関数の使用を考えてみましょう。仮想関数 Draw を持つ基本クラス CTetrisShapeis が インクルードファイル MT5_TetisShape.mqh で定義されています。
//+——————————————————————+ |
また、それぞれの派生クラスでは、この関数は子孫クラスの特性に応じて実施されます。例えば、最初の図形 CTetrisShape1 には独自の Draw() 関数が実装されています。
class CTetrisShape1 : public CTetrisShape |
Square 図形は CTetrisShape6 クラスによって記述され独自の Draw() メソッドを実装します。
class CTetrisShape6 : public CTetrisShape |
作成したオブジェクトが属するクラスに応じて、このクラスの仮想関数かその派生クラスの仮想関数が呼び出されます。
void CTetrisField::NewShape() |
override修飾子 #
override修飾子は、宣言される関数は必ず親クラスのメソッドをオーバーライドする必要があるということを意味しています。この修飾子を使用することで、オーバーライド時のメソッドシグネチャのランダムな変更といったエラーを回避することができます。例として、基底クラスで引数としてint型変数を取るfuncメソッドを定義します。
class CFoo |
次にメソッドを派生クラスでオーバーライドします:
class CBar : public CFoo |
しかしエラーによって引数の型がintからshortに変わります。実際には、このような場合、オーバーライドではなくメソッドのオーバーロードが起こります。オーバーロードされた関数定義のアルゴリズムに従い、特定のケースにおいてコンパイラがオーバーライドされたメソッドの代わりに規定クラスで定義されたメソッドを選択することがあります。
このようなエラーを回避する為には、明示的にオーバーライドメソッドにoverride修飾子を追加する必要があります。
class CBar : public CFoo |
オーバーライド時にメソッドシグネチャが変更された場合、コンパイラは親クラスで全く同じシグネチャを見つけることができず、コンパイルエラーを出します。
‘CBar::func’ method is declared with ‘override’ specifier but does not override any base class method |
final修飾子 #
final修飾子は逆に、派生クラスでのメソッドのオーバーライドを禁止します。メソッドの実装が十分であり、完全に禁止されている場合、派生クラスで変更されないようにするためにfinal修飾子を使って宣言してください。
class CFoo |
final修飾子を使用したメソッドのオーバーライドを試行すると、上記の例で示したように、コンパイルはエラーを出します。
‘CFoo::func’ method declared as ‘final’ cannot be overridden by ‘CBar::func’ |
クラス及び構造体の静的メンパ
静的メンパ
クラスのメンバは、ストレージクラス修飾子 static を使用して宣言することが出来ます。これらのデータメンバはクラスの全てのインスタンスで共有されて 1 つの場所に格納されます。非静的データメンバは、各クラスのオブジェクト変数のために作成されます。
クラスの静的メンバを宣言することが出来なければこれらのデータをプログラムのグローバルレベルで宣言することが必要になります。これはデータとクラス間の関係を破り、クラスでデータとそれらを処理するためのメソッドを結合するという OOP の基本的なパラダイムと一致しないでしょう。静的メンバは特定のインスタンスに固有でないクラスのデータがクラススコープ内に存在することを可能にします。
静的クラスメンバは特定のインスタンスに依存しないため、それへの参照は次のようになされます。
class_name::variable |
ここで class_name がクラス名で variable がクラスメンバ—名です。
ご覧のように、クラスの静的メンバにアクセスするにはコンテキスト解決演算子 ::が使用されます。クラスのメソッド内で静的メンバにアクセスする際にはコンテキスト演算子は任意です。
クラスの静的メンバは、明示的に所望の値で初期化する必要があります。このためには、クラスの静的メンバはグローバルスコープで初期化また宣言されなければなりません。静的メンバの初期化の順序は、そのグローバルスコープでの宣言の順序に対応します。
例えば、テキスト解析クラス CParser があって、処理された単語や文字の合計数を数える必要があるとします。必要なクラスのメンバのみを static として宣言し、それらをグローバルレベルで初期化する必要があります。クラスの全てのインスタンスは、単語や文字の共通のカウンタを使用します。
//+——————————————————————+ |
静的クラスメンバは const キーワードを使用して宣言されます。静的定数はグローバルレベルで const キーワードを使用して初期化されます。
//+——————————————————————+ //— CStack クラスの静的定数の初期化 |
this ポインタ #
キーワード this はそれ自体(つまり、メソッドが実行されたコンテキスト内の特定のクラスインスタンス)に暗黙的に宣言されたポインタを示します。これは、クラスの非静的メソッド内のみで使用することが出来ます。this ポインタはクラスの暗黙的な非静的メンバです。
静的関数では、クラスの静的メンバと静的メソッドのみにアクセスすることが出来ます。
静的メソッド
MQL5 では static 型のメンバ関数が使用出来ます。static 修飾子は、クラス内の宣言では関数の戻り値の型を先行しなければなりません。
class CStack |
const 修飾子を持つメソッドは定数と呼ばれ、そのクラスの暗黙のメンバを変更することは出来ません。クラスの定数関数と定数パラメータの宣言は定数コレクトネス制御と呼ばれます。この制御により、コンパイラがオブジェクトの値の一貫性を確保し、問題がある場合にはコンパイル時にエラーを返すことを確認出来ます。
const 修飾子はクラス宣言内で引数リストの後に配置されます。クラス外での定義も const 修飾子を含む必要があります。
//+——————————————————————+ |
定数制御を使用するもう 1 つの利点は、コンパイラが、定数オブジェクトを読み出し専用メモリに配置するなどの特別な最適化を適応することです。
const 修飾子は関数を呼び出す時にインスタンスメンバの不変を保証するため、静的な関数には const は使用できません。しかし、前述したように、静的関数は非静的クラスメンバにアクセスすることは出来ません。
関数テンプレート
オーバーロードされた関数は一般的に様々なデータ型に対して同様の演算を行うために使用されます。ArraySize() は MQL5 でのそのような関数の簡単な例で、配列のサイズを返します。実際には、このシステム関数はオーバーロードされており、このようなオーバーロードの実装は全体的に MQL5 アプリケーションの開発者から隠されています。
int ArraySize( |
つまり MQL5 言語コンパイラはこの関数の呼び出しごとに必要な実装を挿入するわけです。例えば、整数型の配列には下記が行われます。
int ArraySize( |
ArraySize() は履歴データ形式の相場を操作するための MqlRates 型の配列にしては次のように表示されます。
int ArraySize( |
よって、同様の関数を異なる型の演算をするため使用するのが非常に便利です。しかし、準備作業が必要です。つまり、必要な関数は演算される全てのデータ型に対してオーバーロードされるべきです。
これには便利な解決法があります。それぞれのデータ型に同様の演算が実行される場合には、関数テンプレートの使用が可能です。この場合、プログラマは 1 つの関数テンプレートの記述をする必要があります。テンプレートを記述する際は、関数が作業する明確なデータ型の代わりに一部のみの仮パラメータが指定されます。コンパイラは関数が呼び出される時に使用される引数の型に基づいて自動的に各データ型の適切な取り扱いのための関数を生成します。
関数テンプレートの定義は template キーワードで始まり角括弧内の仮パラメータのリストが続きます。仮パラメータには typename キーワードが先行します。仮パラメータ型は組み込み型またはユーザ定義型です。用例は次の通りです。
- 関数の引数の型を指定する
- 関数の戻り値の型を指定する
- 関数定義内で変数を宣言する
テンプレートパラメータの数は8を超えることは出来ません。テンプレート定義の各仮パラメータは、少なくとも 1 度は関数パラメータのリストに表示されるべきです。仮パラメータの名称は一意である必要があります。
数値型(整数や実数)の配列で最高値を検索する関数テンプレートの例は次の通りです。
template<typename T> T max=arr[0]; |
このテンプレートは、渡された配列の最高値を求める関数を定義し、この値を結果として返します。MQL5 に内蔵された ArrayMaximum() 関数は最高値のインデックスを返すので、それから値そのものを見つけることが可能になります。例:
//— 配列を作成する //—配列内の最大値のインデックスを見つける |
従って、配列の最大値を取得するのに必要な 2 つのステップが実行されました。ArrayMax() 関数テンプレートに適切な型の配列を渡すだけで必要な型の結果を得ることが出来ます。最後の 2 行
//—配列内の最大値のインデックスを見つける |
は、返される結果が関数に渡された配列と同じ型を持っている1行に出来ます。
//— 最大値を見つける |
この場合、ArrayMax() 関数によって返される結果の型は自動的に配列の型と一致されます。
さまざまなデータ型を演算出来る汎用メソッドを作成するために引数の型を文字列として取得するには typename キーワードを使用します。データ型を文字列として返す関数の具体例を考えてみましょう。
#include <Trade\Trade.mqh> |
関数テンプレートはクラスメソッドにも使用することが出来ます。例えば、
class CFile template<typename T> |
関数テンプレートは export、virtual 及び #import キーワードと宣言されるべきではありません。
テンプレート関数の多重定義(オーバーロード)
テンプレート関数の多重定義はしばしば必要です。例えば型キャスティングを使用して1番目のパラメータに2番目のパラメータの値を書き込むテンプレート関数があるとします。MQL5ではstringからboolへの型キャスティングは不可能です。しかし、テンプレート関数の多重定義を作成すれば、それは可能になります。例は下記です。
//+——————————————————————+ |
コード実行の結果、Assign()テンプレート関数はint+stringのペアで使用されていますが、多重定義されたバージョンは2回目の呼び出しでbool+stringのペアですでに使用されています。
string Assign<int,string>(int&,string) |
テンプレートの利点
関数テンプレートは、さまざまなデータ型で配列内の最大要素を検索するなどの同様の操作を実行する必要がある場合に使用されます。テンプレートを適用する主な利点は、型ごとに別々の多重定義(オーバーロード)をコーディングする必要がないことです。複数の多重定義を型ごとに宣言する代わりに、</ t3>
double ArrayMax(double array[]) |
テンプレート関数を1つ書くだけです
template<typename T> |
コ—ド内で使うには:
double high[]; |
ここで、使用されるデータの型を指定する T仮パラメータは、コンパイル時に実際に適用される型に置き換えられます。つまり、コンパイラはdouble、datetimeなどの型ごとに別々の関数を生成します。MQL5では、このアプローチのすべての利点を使ってクラステンプレートを開発することもできます。
クラステンプレート
クラステンプレートはtemplate キーワードの後に typenameキーワードで仮パラメータのリストを列挙する山括弧<> を使用して宣言されます 。このエントリは、クラスを実装するときに実際の変数型を定義するT仮パラメータを持つジェネリッククラスを扱うようにコンパイラに通知します。例としてT 型の要素を持つ配列を格納するためのベクトルクラスを作成しましょう。</ t10>
#define TOSTR(x) #x+” “ // オブジェクト名を表示するマクロ |
次に、さまざまな種類の作業を行うプログラムの3つのTArray オブジェクトを作成するさまざまな方法を適用しましょう。
void OnStart() |
スクリプト実行の結果:
double_array (double:10) |
ここでは、double、int、およびstringの異なるデータ型を持つ3つのベクトルがあります。
クラステンプレートは、任意の型の他のオブジェクトをカプセル化するために設計されるオブジェクトであるコンテナの開発にも適しています。コンテナオブジェクトは既に特定の種類のオブジェクトを含むコレクションです。通常、保存されたデータの処理は、コンテナに即座に組み込まれます。
例えば、配列外の要素にアクセスすることを許可しないクラステンプレートを作成することによって”out of range”(範囲外)重大エラーを避けることができます。
//+——————————————————————+ |
テンプレート宣言は、クラス宣言の外にメソッドを記述する際にも使用する必要がありますのでご注意ください。
template<typename T> |
クラステンプレートと関数テンプレートを使用すると、カンマで区切られた複数の仮パラメータの定義が可能です。下記の例は”key – value”のペアを格納するMapコレクショです。
template<typename Key, template Value> |
抽象クラスと純粋仮想関数
抽象クラスは、将来におけるより具体的な派生クラスを作成する一般的なエンティティを作成する為に作られています。抽象クラスは、いくつかの他のクラスの為に基底クラスとして使用することができるだけなので、抽象クラスのオブジェクトタイプを作成することはできません。
純粋仮想関数を一つでも含むクラスは、抽象クラスとなります。したがって、抽象クラスから派生したクラスは、その純粋仮想関数を実装する必要があり、そうしないと、これらは同様に抽象クラスになります。
仮想関数は、純粋指定子構文を使用し、『純粋』として宣言されます。例として、共通機能を提供する為だけ型に作成されるCAnimalクラスを見てみましょう。CAnimalのオブジェクト自体は、実際の使用には一般的すぎます。したがって、CAnimalクラスは、抽象クラスの為の良い例となります。
class CAnimal |
ここでのSound()関数は、純粋仮想関数PURE(=0)の指定子で宣言されている為、純粋仮想となります。
(=NULL)または(=0)と純粋指定子PUREが指定されている仮想関数のみが純粋仮想関数となります。抽象クラスの使用と宣言例:
class CAnimal //— 間違った使用例 //— 正しい使用例 |
抽象クラス使用の制限
純粋仮想関数(直接または間接)の抽象クラスのコンストラクタによる呼び出しの際、結果は未定義になります。
//+——————————————————————+ |
しかしながら、抽象クラスのコンストラクタとデストラクタは、他のメンバー関数を呼び出すことができます。
Originally posted 2019-07-27 10:30:00.