Version 11.21
「演算子のオーバーロード」の意味
「今回も前回の続き、演算子のオーバーロードについて見ていきます」
『え? 前回でだいたいやっちゃったと思うんだけど』
「ところがそうじゃないんです。演算子のオーバーロードは、奥が深いって
いうか、複雑というか、ややこしいというか……」
『よーするにアレなのね、アレ』
「まぁそんなところ。というわけで、さらにややこしい部分について見てい
きます。まず、新しく CTestString ってクラスを作ることを考えます。こ
のクラスは」
CTestString cTestString = "あいうえお";
TRACE( "%s\n", cTestString.GetData() );
// あいうえお
「という感じに、コンストラクタで文字列を渡すと中に持って、
GetData() で取得できる、っていうクラスとします」
『うん』
「これに + 演算子のオーバーロードを持たせるとします」
CString cStr;
cStr = cTestString + "かきくけこ";
TRACE( "%s\n", cStr );
// あいうえおかきくけこ
『こーゆーのって CString ではできるよね』
「そう。まぁここから説明するのは全部 CString でもしてることだからそ
れを参考にしてもいいんだけど、それだと作る練習にはならないから」
『はーい』
「で、実際にこの機能を CTestString に入れると、 CTestString はこうな
ります」
class CTestString
{
private:
CString m_cStr;
public:
// コンストラクタ。
CTestString( CString p_cStr )
{
m_cStr = p_cStr;
}
// + 演算子。
CString operator + ( CString p_cStr )
{
return m_cStr + p_cStr;
}
// データ出力。
CString GetData() const
{
return m_cStr;
}
};
『うん、この辺は前とほとんど同じだからわかる……けど?』
「そう、ここまでは復習段階。で、今回説明するのは」
cStr = "かきくけこ" + cTestString;
「ってできるようにする方法」
『できないの?』
「うん、ビルドしてみて」
『エラーになった』
error C2679:
二項演算子 '+' : 型 'class B1::CTestString' の右オペランドを扱う
演算子は定義されていません。(または変換できません)
『どういうこと?』
「つまり〈 文字列 + CTestString 〉はできませんよってこと。実は、二項
演算子のオーバーロード関数は、演算子の右側に値が来ないと呼ばれないん
です」
『んーつまり』
CString operator + ( CString p_cStr )
『だったら』
cStr = cTestString + "かきくけこ";
『じゃなきゃいけないってこと?』
「そういうこと」
『 i++ で呼ばれるのは?』
「それは単項演算子だから別。というかそれはかなり特別だし」
『型変換のも別?』
「そう、あれも別」
『つまり、 + とか = とか、そういう二項演算子系は、左側に自分のクラス
を、右側に渡す値が来ないと呼べないんだ』
「そういうこと」
『でも、 CString はできる、だから方法はあるんだよね』
「もちろん。それをこれから説明します。二項演算子の制限は、クラスのメ
ンバ関数だったから。で、実は演算子のオーバーロードは普通の関数として
も作ることができるんです」
『普通の関数って、メンバ関数じゃない、クラスの中にない関数ってこ
と?』
「そういうこと。たとえばこんな感じに」
/**
+ 演算子のオーバーロード。
グローバル関数版。
*/
CString operator +
( CString p_cLStr
, CTestString p_cRTestString
)
{
return p_cLStr + p_cRTestString.GetData();
}
『なんか変……』
「関数名とかなくていきなり operator だからね。でも、基本的にはメンバ
関数のものと同じ。違うのは、引数が多いこと」
『二項演算子だから、引数がふたつってこと?』
「そういうこと。使用例はこんな感じ」
// 使用例。
void Use_GlobalPlus()
{
CTestString cTestString = "あいうえお";
TRACE( "%s\n", cTestString.GetData() );
// あいうえお
CString cStr;
cStr = "かきくけこ" + cTestString;
TRACE( "%s\n", cStr );
// あいうえおかきくけこ
}
「この中の」
cStr = "かきくけこ" + cTestString;
「は、さっきの関数の」
CString operator +
( CString p_cLStr // < "かきくけこ"
, CTestString p_cRTestString // < cTestString
)
「に入るんです」
『で、中で p_cRTestString.GetData() 呼んで文字列取り出して、
CString どうしだから足して、それを返してる……と』
「そういうこと。これとメンバ関数版の両方を用意すれば」
『 + は完璧ってことね。……? もしかして、演算子のオーバーロードを
するときって、かなり大変?』
「大変だね。ひととおりの演算子に対応させるには、それだけの数、関数を
用意しなきゃいけないからね」
『めんどくさー』
「でも、たとえば上の例、 - 演算子は必要だと思う?」
『文字列から文字列を引く……なんかイメージわかないね』
「そういう場合には無理に作る必要ないから」
『必要なものだけ作ればそんなに大変じゃないわけね』
「さて、ここで注意。たとえばこういうのは作れません」
/**
+ 演算子のオーバーロード。
*/
int operator +
( int p_iL
, int p_iR
)
{
TRACE( "%d + %d = %d", p_iL, p_iR, p_iL + p_iR );
return p_iL + p_iR;
}
『引数ふたつとも int ……って、それって普通の + と同じじゃない』
「そう。たとえばこういう例みたいに、普通の + 演算子をオーバーロード
して、その中にデバッグ機能を入れたいなーとかって場合もあるわけです」
『確かにこれはちょっと便利そう』
「でもこれはできないんです」
error C2803:
'operator +' の宣言で、クラス型のパラメータが 1 つも
指定されていません。
『ってコンパイルエラーが出た』
「演算子のオーバーロードをするときには、かならず引数のどれかがクラス
じゃないといけないんです」
『クラスじゃない、つまり両方とも int はだめ、と。ってことは、元々あ
るのは変えられないってことね』
「そういうこと。で、ここからはちょっと脱線というか、難しい話なんだけ
ど」
『?』
/**
+ 演算子。
*/
int operator +
( int p_iL
, int p_iR
)
{
return p_iL + p_iR;
}
『なにこれ。てか普通の + だね』
「プログラマー的な考え方をした場合、こういう関数がどっかに隠れてる、
って考えて欲しいかなと」
『……どゆこと?』
「たとえば」
int i = 100 + 200;
「って書いてあったら、どっかにあるこの関数が呼ばれてるんだって」
『そう考えるってこと?』
「そう。そうすると、なんで〈演算子の【オーバーロード】〉なのかって」
『あ……そか、もしこういう関数があるなら、その引数違いの関数を作るっ
てことになるから』
「そう、納得できるでしょ?」
『でもこじつけっぽい』
「う”」
/*
Preview Next Story!
*/
『オーバーロードって複雑ねー、使うだけなら便利なんだけど』
「それって危険かも」
『そなの?』
「使えるだけで仕組みを知ってないと、危険なことって結構あるよ」
『 CString なんてすごく簡単に使えそうだけど』
「というわけで次回」
< Version 11.22 CString の注意点 >
『につづく!』
「蛇口をひねると水が出る、ってだけじゃダメってこと」
『ダメなの?』
「たぶん……」