ホームに戻る
関連 :
目次 :
オーバーロード(Overload)とは
引数の型や数が異なる同名の関数を定義すること。同様の機能に対して異なるインタフェースを提供することができる。
「オーバーライド」との混同に注意。
// (C++) myabs() 関数を二通りにオーバーロード
int myabs(int a) { return a<0 ? -a : a; }
float myabs(float a) { return a<0 ? -a : a; }
void main()
{
// どちらの myabs() を呼ぶかは、コンパイラが引数の型から自動判別してくれる
cout << "-10の絶対値" << myabs(-10) << "\n"; //< int 引数の myabs()
cout << "9.3の絶対値" << myabs(9.3) << "\n"; //< float 引数の myabs()
}
Cではオーバーロードがサポートされていないため、同様の機能を持つ関数を定義する場合には異なる名前を割り当てる必要があった。
C++(やその他のモダン言語)ではオーバーロードにより同名の関数を定義することができるため、関数管理の煩雑さが軽減できる。
同名で定義された関数のいずれを呼ぶかは、引数の型・数からコンパイラが自動判別する ため、一種のポリモーフィズムを実現できる。
// (C) 別名で定義 ⇒ 引数によって使い分けなければならず、覚えるべき関数が増える
int myabs_int(int a) { return a<0 ? -a : a; }
float myabs_float(float a) { return a<0 ? -a : a; }
(上記の例では引数の型によらずロジックは共通であるため、オーバーロードよりもテンプレートが適している。)
オーバーロードにおける制約
関数をオーバーロードする際には、 引数の型・数のいずれか(もしくは両方)が異なっている必要がある 。
また、関数内で(同名の)オーバーロード関数を呼び出すことに制約は無い。
struct St_Date
{
int year;
int month;
int day;
}
void putDate(int year, int month, int day); //< 前方宣言
// 構造体引数を取る putDate()
void putDate(St_Date date)
{
// 関数内でオーバーロード関数をコール
putDate(date.year, date.month, date.day);
}
// 年、月、日を個別に指定する putDate()
void putDate(int year, int month, int day)
{
cout << year << "/" << month << "/" << day << "\n"
}
デフォルト引数
呼び出し時に実引数が指定されていないときに、デフォルト値を充てることができる。
これは デフォルト引数 と呼ばれ、関数オーバーロードに関連する機能である。
// 第2引数 (b) にデフォルト値を適用
int f(int a, int b = 0)
{
:
}
:
f(3, 7); //< a = 3, b = 7 (b に値を指定)
f(4); //< a = 4, b = 0 (b は指定されていないため、デフォルト値が充てられる)
上記の例では、関数 f() の2つの仮引数のうち b にはデフォルト値 ( 0 ) が割り当てられているため、呼び出し時に省略が可能である。
その際、b の実引数にはデフォルト値の 0 が充てられる。
デフォルト引数を用いると、別関数を定義することなく引数の数を疑似的に変更できるため、オーバーロードを用いるよりも簡便であることが多い。
但し引数の型が異なる場合など、オーバーロードを使うべき局面も多いため、使い分けが必要である。
デフォルト引数の制約
int f1(int a , int b = 0, int c = 0); //< 第2引数以降にデフォルト値を適用
int f2(int a = 0, int b , int c = 0); //< この関数はエラーとなる
:
f1(3, 1); //< 第1、第2引数を指定(第3引数を省略)
f1(4); //< 第1引数を指定(第2引数以降を省略)
デフォルト引数はすべて、デフォルト値を持たない仮引数より右に設定しなければならない 。上記の f1() では 第2引数 (b) にデフォルト値を設定しているため、第3引数(以降)もデフォルト値を設定する必要がある。
即ち、 第1引数をデフォルト化した場合、すべての引数をデフォルト化する必要がある 。
f2() は第1引数 (a) にデフォルト値が設定されているが、第2引数はデフォルト値が設定されていないためコンパイルが通らない。
呼び出し時も同様に、ある引数を省略した場合、以降のすべての引数を省略することとなる。
(上記の f1() において、b を省略して c を指定する、ということはできない。C#などでは引数のラベルを指定できるため、この制約は無い。)
オーバーロードの曖昧さ
いずれのオーバーロード関数をコールすべきかをコンパイラが判断できない(曖昧な)状況が発生することがある。
自動型変換による曖昧さ
// f() 関数のオーバーロード
float f(float a);
double f(double a);
:
float fVal = 3.14;
double dVal = 3.14;
f(fVal); //< 引数の型が float と判明しているため曖昧ではない
f(dVal); //< 引数の型が double と判明しているため曖昧ではない
f(10); //< 引数を float 、double のどちらにも変換できる ⇒ 曖昧
この例では、f() の実装自体は曖昧ではない。
引数の型が明確となっていれば特に問題は生じないが、10 という数値(整数)は float にも double にも変換できるため、どちらのオーバーロード関数を呼ぶべきかをコンパイラが判断できない。
⇒ 引数をキャストするなどして解決を図る。
本質的な曖昧さ
参照仮引数を用いる場合
// f() 関数のオーバーロード
void f(int a); //< 値仮引数
void f(int& a); //< 参照仮引数
:
int i = 10;
f(i); //< 曖昧
この例はオーバーロード関数の実装自体が曖昧である。値仮引数を受け取る関数と、参照仮引数を受け取る関数では、呼び出しに構文上の違いが無いためである。
デフォルト引数との併用
// f() 関数のオーバーロード
void f(int a);
void f(int a, int b = 0);
:
int i = 10;
f(i); //< 曖昧
この場合も関数の実装自体が曖昧であり、回避手段は存在しない。
オーバーロード関数のアドレス
// f() 関数のオーバーロード
void f(int a); //< *1
void f(int a, int b); //< *2
:
// 関数ポインタの宣言
void (*fp1)(int a); //< *1 に適合
void (*fp2)(int a, int b); //< *2 に適合
// 関数ポインタへの代入
fp1 = f; //< *1 が代入される
fp2 = f; //< *2 が代入される
オーバーロード関数のアドレスを関数ポインタに代入する際は、 引数の型、および数がポインタの宣言に合致した関数が選択される 。
逆にポインタの宣言がいずれの関数宣言にも合致しない場合はコンパイルエラーとなる。
余談
C++の旧い仕様においては、オーバーロード関数を定義する際は overload キーワードを付与する必要があった。
現在ではキーワードの付与は任意であるが、オーバーロード関数であることを明示できるため、ヒューマンエラー防止の観点では有用である。