PrintInt、PrintFloat、PrintStringと、中身が似た関数を、型ごとに別の名前で作っていくのは、面倒です。関数のオーバーロードを使うと、同じ名前のまま、複数の型に対応できます。
この記事では、関数のオーバーロードの意味と書き方、ゲームでの使いどころ、初心者がハマりやすいミスを解説します。
関数のオーバーロードとは?
関数のオーバーロードとは、同じ名前で、引数が違う関数を複数定義できる仕組みです。呼び出したときの引数を見て、コンパイラがぴったり合う関数を自動で選んでくれます。
#include <iostream>
#include <string>
// 同じ名前で引数違いの関数を用意する
void Print(int n) { std::cout << "整数: " << n << "\n"; }
void Print(double d) { std::cout << "小数: " << d << "\n"; }
void Print(const std::string& s) { std::cout << "文字列: " << s << "\n"; }
int main() {
Print(10); // 整数版が呼ばれる
Print(3.14); // 小数版
Print("Hello"); // 文字列版
return 0;
}整数: 10
小数: 3.14
文字列: Hello
すべて Print という同じ名前なのに、渡した型に合わせて正しい関数が選ばれています。文字列の扱いはstringとchar配列の違いの記事もどうぞ。
なぜ関数のオーバーロードが必要?
理由は、呼ぶ側が名前を覚えやすく、コードが読みやすくなるからです。PrintInt PrintDouble …と別名を作ると、使うたびに正しい名前を探す手間がかかります。同じ役割は同じ名前にできるのがオーバーロードの強みです。
実践例:ゲームでの使い方
ゲームでは、同じ「生成する」処理でも対象が違うことがあります。オーバーロードなら Spawn という名前で統一できます。
void Spawn(int enemyId) { /* 敵を生成 */ }
void Spawn(int x, int y) { /* 座標にアイテム生成 */ }
void Spawn(const std::string& s) { /* 名前でオブジェクト生成 */ }引数の数や型が違えば、同じ Spawn でも別の関数として使い分けられます。クラス設計の基本はクラスとは?の記事もあわせてどうぞ。
【重要】私が実際に関数オーバーロードで困った体験談
個人開発で void Damage(int) と void Damage(float) を作っていたとき、Damage(0) と呼んだら「あいまいな呼び出しです(ambiguous)」というコンパイルエラーが出て固まりました。
…と思いきや今度は別の場面で、Damage(0.0) のつもりが 0 を渡してしまい、意図せず int 版が呼ばれて挙動がズレていたこともありました。原因はどちらも数値の暗黙変換。0.0f のように型をはっきり書いて渡すようにしたら、迷わず正しい関数が選ばれるようになりました。
関数オーバーロード使用時のよくある失敗例と対処法
①戻り値の型だけ違うのはNG
int f() と double f() のように戻り値だけ違うのはオーバーロードできません。区別は「引数」で行うと覚えましょう。
②暗黙変換で「あいまい」エラー
int と double の両方があると、どちらにも変換できる値で迷います。型をはっきり書いて渡す(0.0f など)と回避できます。
③引数が実質同じでオーバーロードにならない
引数の数も型も同じだと、ただの再定義エラーです。オーバーロードには引数に違いが必要です。
注意点
- 区別は引数の数・型で行う(戻り値だけでは不可)
- あいまいさを避けるため、型を明示して渡す
- 「あらゆる型で同じ処理」をしたいならテンプレートも検討する
要点の整理
- 関数オーバーロードは同じ名前で引数違いの関数を作る仕組み
- コンパイラが引数を見てぴったりの関数を選ぶ
- 区別は引数で。戻り値だけでは区別できない
- 暗黙変換の「あいまい」に注意し、型を明示する
コンパイラは、引数を見て、ぴったりの関数を選びます。区別できるのは引数だけで、戻り値だけが違う関数は、作れません。暗黙の型変換で「あいまい」と言われたときは、型を明示して呼びます。


