関数の多重定義(オーバーロード)

通常、関数名にはその主な目的を反映する傾向があります。原則として、人間によって読まれるのが可能なプログラムには、うまく選択された 識別子が含まれます。しかし、たまに 1 つの目的のために複数の関数が使用されることがあります。例として、double の配列の平均値を算出する関数と整数の配列の平均値を算出する関数について考えましょう。両方とも AverageFromArray と呼ぶのが便利です。

//+------------------------------------------------------------------+
//| double 型配列の平均値の計算                                          |
//+------------------------------------------------------------------+
double AverageFromArray(const double & array[],int size)
 {
  if(size<=0) return 0.0;
  double sum=0.0;
  double aver;
//---
  for(int i=0;i<size;i++)
    {
     sum+=array[i];   // double の合計
    }
  aver=sum/size;       // 合計を要素数で割る
//---
  Print("Calculation of the average for an array of double type");
  return aver;
 }
//+------------------------------------------------------------------+
//| int 型配列の平均値の計算                                             |
//+------------------------------------------------------------------+
double AverageFromArray(const int & array[],int size)
 {
  if(size<=0) return 0.0;
  double aver=0.0;
  int sum=0;
//---
  for(int i=0;i<size;i++)
    {
     sum+=array[i];     // int の合計
    }
  aver=(double)sum/size;// double 型にキャストしてから割る
//---
  Print("Calculation of the average for an array of int type");
  return aver;
 }

どちらの関数も Print() 関数を使用してメッセージを出力します。

  Print("Calculation of the average for an array of int type");

コンパイラは、引数の型と数に応じて必要な関数を選択します。このような選択ルールは署名マッチングアルゴリズムと呼ばれます。署名とは、関数宣言で使用される型のリストです。

例:

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  int    a[5]={1,2,3,4,5};
  double b[5]={1.1,2.2,3.3,4.4,5.5};
  double int_aver=AverageFromArray(a,5);
  double double_aver=AverageFromArray(b,5);
  Print("int_aver = ",int_aver,"   double_aver = ",double_aver);
 }
//--- スクリプトの結果
// Calculate the average for an array of int type
// Calculate the average for an array of double type
// int_aver= 3.00000000    double_aver= 3.30000000

関数オーバーロードとは、パラメータが異なるいくつかの同名の関数を作成する過程です。つまり、関数のオーバーロードバリアントでは引数の数及び/またはその型は異なっていなければなりません。特定の関数バリアントは、関数宣言のパラメータのリストと関数を呼び出す引数のリストの対応関係に基づいて選択されます。

オーバーロードされた関数が呼び出されると、コンパイラに適切な関数を選択するためのアルゴリズムが存在する必要があります。この選択アルゴリズムは存在する型のキャストに基づきます。最良のマッチは一意である必要があります。オーバーロードされた関数は、少なくとも 1 つの引数にとって全てのバリアントの内で最良のマッチである必要があります。同時に、他の全ての引数に対しての一致も他のバリアントに劣らない必要があります。

以下は各引数のマッチングアルゴリズムです。

オーバーロード関数選択アルゴリズム

  1. (可能な場合)厳密なマッチングを使用します。
  2. 標準型の増加を試します。
  3. 標準型キャストを試します。

標準型の増加は、他の標準的な変換よりも優れています。増加とは floatdoubleboolcharshort 及び enumintに変換することです。類似の 整数型 配列の型キャストも型キャストに属します。類似の型は次の通りです。シングルバイト整数である bool、char、及び uchar の 3 型。ダブルバイト整数である short と ushort。 4 バイト整数の int、uint 及び color。long、ulong、及び datetime。

厳密なマッチングが一番です。このような一貫性を達成するために型キャスト を使用することが出来ます。コンパイラは、あいまいな状況に対処することは出来ません。従って、オーバーロード関数をあいまいにする型の微妙な違いや暗黙の変換に依存してはいけません。

はっきりしない場合には厳格な遵守を確保するために明示的な変換を使用してください。

MQL5 におけるオーバーロード関数の例は ArrayInitialize() 関数の例でご覧になれます。

関数オーバーロードの規則はクラスメソッドのオーバーロードにも適応されます。

 

システム関数のオーバーロードは可能ですが、コンパイラが正確に必要な関数を選択することが出来なければいけません。例えば、システム関数 MathMax() のオーバーロードの仕方は 4 つありますが、そのうち2 つのみが正しいものです。

例:

// 1. オーバーロードは可能 - 関数は内蔵された MathMax() と違う数のパラメータを持つ
double MathMax(double a,double b,double c);
 
// 2. オーバーロードは不可能
// パラメータ数が異なるが、最終パラメータに初期値がある
// これは呼び出す際にシステム関数の隠蔽につながり、容認されない
double MathMax(double a,double b,double c=DBL_MIN);
 
// 3. オーバーロードは可能 - パラメータ a と b の型による通常のオーバーロード
double MathMax(int a,int b);
 
// 4. オーバーロードは不可能
// パラメータの数と種類が元の double MathMax(double a,double b) と同じ
int MathMax(double a,double b);

参照

オーバーロード仮想関数ポリモーフィズム