テンプレートの利点

関数テンプレートは、さまざまなデータ型で配列内の最大要素を検索するなどの同様の操作を実行する必要がある場合に使用されます。テンプレートを適用する主な利点は、型ごとに別々の多重定義(オーバーロード)をコーディングする必要がないことです。複数の多重定義を型ごとに宣言する代わりに、</ t3>

double ArrayMax(double array[])
 {
  ...
 }
int ArrayMax(int array[])
 {
  ...
 }
uint ArrayMax(uint array[])
 {
  ...
 }
long ArrayMax(long array[])
 {
  ...
 }
datetime ArrayMax(datetime array[])
 {
  ...
 }

テンプレート関数を1つ書くだけです

template<typename T>
T ArrayMax(T array[])
 {
  if(ArraySize()==0)
    return(0);
  uint max_index=ArrayMaximum(array);  
  return(array[max_index]);
 }

コ—ド内で使うには:

double high[];
datetime time[];
....
double max_high=ArrayMax(high);
datetime lasttime=ArrayMax(time);

ここで、使用されるデータの型を指定する T仮パラメータは、コンパイル時に実際に適用される型に置き換えられます。つまり、コンパイラはdoubledatetimeなどの型ごとに別々の関数を生成します。MQL5では、このアプローチのすべての利点を使ってクラステンプレートを開発することもできます。

クラステンプレート

クラステンプレートはtemplate キーワードの後に typenameキーワードで仮パラメータのリストを列挙する山括弧<> を使用して宣言されます 。このエントリは、クラスを実装するときに実際の変数型を定義するT仮パラメータを持つジェネリッククラスを扱うようにコンパイラに通知します。例としてT 型の要素を持つ配列を格納するためのベクトルクラスを作成しましょう。</ t10>

#define TOSTR(x) #x+" "   // オブジェクト名を表示するマクロ
//+------------------------------------------------------------------+
//| T型要素を格納するためのベクトルクラス                                       |
//+------------------------------------------------------------------+
template <typename T>
class TArray
 {
protected:
  T                 m_array[];
public:
  //--- コンストラクタはデフォルトで10要素の配列を作成する
  void TArray(void){ArrayResize(m_array,10);}
  //--- 指定された配列サイズのベクトルを作成するコンストラクタ
  void TArray(int size){ArrayResize(m_array,size);}
  //--- TArray型オブジェクトに格納されているデータの型と量を返す
  string Type(void){return(typename(m_array[0])+":"+(string)ArraySize(m_array));};
 };

次に、さまざまな種類の作業を行うプログラムの3つのTArray オブジェクトを作成するさまざまな方法を適用しましょう。

void OnStart()
 {
  TArray<double> double_array;   // ベクトルのデフォルトサイズは10
  TArray<int> int_array(15);     // ベクトルのサイズは15
  TArray<string> *string_array; // TArray<string>ベクトルへのポインタ
//--- 動的オブジェクトを作成する
  string_array=new TArray<string>(20);
//--- 操作ログにオブジェクト名、データ型、ベクトルサイズを表示する
  PrintFormat("%s (%s)",TOSTR(double_array),double_array.Type());
  PrintFormat("%s (%s)",TOSTR(int_array),int_array.Type());
  PrintFormat("%s (%s)",TOSTR(string_array),string_array.Type());
//--- プログラムを完了する前に動的オブジェクトを削除する
  delete(string_array);  
 }

スクリプト実行の結果:

 double_array  (double:10)
 int_array  (int:15)
 string_array  (string:20)

ここでは、double、int、およびstringの異なるデータ型を持つ3つのベクトルがあります。

クラステンプレートは、任意の型の他のオブジェクトをカプセル化するために設計されるオブジェクトであるコンテナの開発にも適しています。コンテナオブジェクトは既に特定の種類のオブジェクトを含むコレクションです。通常、保存されたデータの処理は、コンテナに即座に組み込まれます。

例えば、配列外の要素にアクセスすることを許可しないクラステンプレートを作成することによって"out of range"(範囲外)重大エラーを避けることができます。

//+------------------------------------------------------------------+
//| 配列要素への自由なアクセスのためのクラス                                    |
//+------------------------------------------------------------------+
template<typename T>
class TSafeArray
 {
protected:
  T                 m_array[];
public:
  //--- デフォルトコンストラクタ
  void              TSafeArray(void){}
  //--- 指定されたサイズの配列を作成するコンストラクタ
  void              TSafeArray(int size){ArrayResize(m_array,size);}
  //--- 配列サイズ
  int               Size(void){return(ArraySize(m_array));}
  //--- 配列サイズを変更する
  int               Resize(int size,int reserve){return(ArrayResize(m_array,size,reserve));}
  //--- 配列を解放する
  void              Erase(void){ZeroMemory(m_array);}
  //--- インデックスによって配列要素にアクセスする演算子
  T                 operator[](int index);
  //--- 配列からすべての要素を一度に受け取る代入演算子
  void             operator=(const T  &array[]); // T type array
 };
//+------------------------------------------------------------------+
//| インデックスによる要素の受け取り                                           |
//+------------------------------------------------------------------+
template<typename T>
T TSafeArray::operator[](int index)
 {
  static T invalid_value;
//---
  int max=ArraySize(m_array)-1;
  if(index<0 || index>=ArraySize(m_array))
    {
    PrintFormat("%s index %d is not in range (0-%d)!",__FUNCTION__,index,max);
    return(invalid_value);
    }
//---
  return(m_array[index]);
 }
//+------------------------------------------------------------------+
//| 配列の代入                                                         |
//+------------------------------------------------------------------+
template<typename T>
void TSafeArray::operator=(const T  &array[])
 {
  int size=ArraySize(array);
  ArrayResize(m_array,size);
//--- T型はコピーオペレータをサポートすべき
  for(int i=0;i<size;i++)
     m_array[i]=array[i];
//---
 }
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
  int copied,size=15;  
  MqlRates rates[];
//--- 相場の配列をコピーする
  if((copied=CopyRates(_Symbol,_Period,0,size,rates))!=size)
    {
    PrintFormat("CopyRates(%s,%s,0,%d) returned %d error code",
    _Symbol,EnumToString(_Period),size,GetLastError());
    return;
    }
//--- コンテナを作成し、それにMqlRatesの値配列を挿入する
  TSafeArray<MqlRates> safe_rates;
  safe_rates=rates;
  //--- 配列内のインデックス
  int index=3;
  PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
  //--- 配列外のインデックス
  index=size;
  PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
 }

テンプレート宣言は、クラス宣言の外にメソッドを記述する際にも使用する必要がありますのでご注意ください。

template<typename T>
T TSafeArray::operator[](int index)
 {
  ...
 }
template<typename T>
void TSafeArray::operator=(const T  &array[])
 {
  ...
 }

クラステンプレートと関数テンプレートを使用すると、カンマで区切られた複数の仮パラメータの定義が可能です。下記の例は"key – value"のペアを格納するMapコレクショです。

template<typename Key, template Value>
class TMap
 {
  ...
 }

 

参照

関数テンプレート多重定義(オーバーロード)