Version 11.19
オーバーロード関数を作ってみよう
「今回はオーバーロードについて見てみます」
『オーバーロードって前にやったね』
「 Version 8.11 ( No.153 ) だね。オーバーロードそのものについては
そっちで解説してるからいいとして、今回は」
『作ってみる?』
「そゆこと。まずは簡単な例を見てみます」
// オーバーロードしたメンバ関数を持つクラス。
class CHasOverload
{
public:
// メンバ関数1。
void Overloaded()
{
TRACE( "Overloaded()\n" );
}
// メンバ関数2。
void Overloaded( int p_i )
{
TRACE( "Overloaded( %d )\n", p_i );
}
};
『 Overloaded() ってメンバ関数がふたつあるから、これがオーバーロード
してるんだね』
「そう。戻り値が同じで、引数が違うメンバ関数が、オーバーロードされた
メンバ関数。これの使用例はこんな感じ」
void Use_CHasOverload()
{
CHasOverload cHasOverload;
cHasOverload.Overloaded();
// Overloaded()
cHasOverload.Overloaded( 100 );
// Overloaded( 100 )
}
『引数があるのとないのとで別のが呼ばれるわけね』
「これは一番わかりやすい例。引数の数が違うから、どっちが呼ばれるかっ
ていうのは一目瞭然だからね」
『そか、引数の数が同じで、型が違うっていうのもオーバーロードなんだ』
「というわけでその例」
// オーバーロードしたメンバ関数を持つクラス2。
class CHasOverload_SameType
{
public:
// メンバ関数1。
void Overloaded( LPCTSTR p_pch )
{
TRACE( "Overloaded( %s )\n", p_pch );
}
// メンバ関数2。
void Overloaded( int p_i )
{
TRACE( "Overloaded( %d )\n", p_i );
}
};
『上は文字列で、下は整数ね』
「使用例はこんな感じ」
void Use_CHasOverload_SameType()
{
CHasOverload_SameType cHasOverload;
cHasOverload.Overloaded( "テスト" );
// Overloaded( テスト )
cHasOverload.Overloaded( 100 );
// Overloaded( 100 )
}
『なるほど、文字列を渡すか、数値を渡すかでどっちの関数が呼ばれるかが
決まるわけねー、これは便利かも』
「ただ、この自動的に決まる、っていうのは実は危険な面もあるんです」
『危険?』
「たとえば、文字列が NULL だった場合」
void Use_CHasOverload_SameType()
{
CHasOverload_SameType cHasOverload;
cHasOverload.Overloaded( NULL );
// Overloaded( 0 )
cHasOverload.Overloaded( 100 );
// Overloaded( 100 )
}
『……あれ? もしかして両方とも int の方が呼ばれてる?』
「そうなんです。 Version 5.11 ( No.076 ) で教えたように、 NULL は数
字の 0 だから int の方が呼ばれちゃうんです」
『え? NULL ってアドレスの 0 ってことじゃなかったの?』
「さっきの No.076 でも触れてるけど、 NULL って、ホントの数字の 0 な
んです」
『えーでもさ』
int *pi = 0x10000000;
『みたいに、ポインタにアドレスの数字って直接入れられないよね』
「うん、ダメだよ、コンパイルエラーになるから。ところが、実は〈 0 だ
けはどんなポインタにも入れることができる〉っていう特例があるんです」
『ず、ずるい……』
「確かにね。だからこそ、上のような問題が出てくるわけ」
『あ、やっぱ問題なんだ』
「そう。だから、本当は数字と文字列でオーバーロードするのは危険なこと
なんです。結構多いけどね」
『多い?』
「 CString でもしてるし」
『げ』
「 No.076 で〈 NULL じゃなくて 0 って書く人もいる〉のは、そういう紛
らわしさをできるだけ取り除くためなんです」
『そか、上の例でも NULL じゃなくて 0 ならわかるもんね』
「あと、オーバーロードしてる関数は、あまり動作を変えないようにするの
がコツかな」
『どーゆーこと?』
「上の例だとどっちも TRACE() で出してるだけだけど、これがもし、片方
は TRACE() でもう片方はファイルに出力してたら」
『げ、それはヤダね』
「だから、オーバーロードは〈引数の型が違っても同じ結果になる〉ってい
う場合にするのがいいと思うよ」
『結果が違う場合は?』
「メンバ関数の名前を変えればいいでしょ」
『あ、そっか』
「最後に、オーバーロードした関数の戻り値について。オーバーロードした
時にはそれぞれ別の戻り値の型にすることができます」
// オーバーロードしたメンバ関数を持つクラス3。
class CHasOverload_OtherReturn
{
public:
// メンバ関数1。
void Overloaded( LPCTSTR p_pch )
{
TRACE( "Overloaded( %s )\n", p_pch );
}
// メンバ関数2。
int Overloaded( int p_i )
{
TRACE( "Overloaded( %d )\n", p_i );
return p_i;
}
};
『片方は void でもう片方は int ね』
「使う場合にはこんな感じ」
void Use_CHasOverload_OtherReturn()
{
CHasOverload_OtherReturn cHasOverload;
cHasOverload.Overloaded( "テスト" );
// Overloaded( "テスト" )
int i = cHasOverload.Overloaded( 100 );
// Overloaded( 100 )
TRACE( "%d\n", i );
// 100
}
『もし文字列の方で int を受け取ろうとしたら?』
「そうするとコンパイルエラーになります」
int i = cHasOverload.Overloaded( "テスト" );
// error C2440: 'initializing' :
// 'void' から 'int' に変換することはできません。
// void 型の式は他の型へ変換できません。
『へー』
「これはとっても重要なこと。実は、どの関数が呼ばれるかは引数でしか決
められないんです。言い換えると、戻り値でオーバーロードすることはでき
ないってこと」
『?』
「たとえば」
// オーバーロードしたメンバ関数を持つクラス4。
class CHasOverload_OtherReturn_Bad
{
public:
// メンバ関数1。
void Overloaded()
{
TRACE( "Overloaded()\n" );
}
// メンバ関数2。
int Overloaded()
{
TRACE( "Overloaded()\n" );
return 100;
}
};
『引数がなくて、戻り値が void と int ……あ、戻り値でオーバーロード
ってこういうことね』
「これをビルドしてみると」
『げ、コンパイルエラー出まくり』
error C2556:
'int __thiscall B1::CHasOverload_OtherReturn_Bad::Overloaded(void)'
: オーバーロード関数の戻り値は異なっていますが、
引数リストは同一です。
「というわけで、戻り値だけ変えたり、戻り値でどの関数を呼ぶか決めさせ
たりっていうのはできません」
『ちょっと不便かも〜』
/*
Preview Next Story!
*/
『便利だけど危険、って難しいね』
「これからはそういうの増えてくからね」
『むむむ』
「でもそういう世界に踏み込むと、一人前のプログラマーって言えるかも」
『い、いつのまにそんなとこまで???』
「遠回りしたけど結構身に付いてると思うよ」
『へ〜』
「というわけで次回」
< Version 11.20 演算子をオーバーロードしよう >
『につづく!』
「でもまだ半分も教えてないけど」
『げ!』