Version 16.18
単項演算子のオーバーロード
「前回は、演算子のオーバーロードの戻り値について説明しました」
『参照にしている理由ってやつね』
「今回は、単項演算子のオーバーロードをしてみます」
『単項演算子ってことは、オペランドがひとつのだよね、 ++ 演算子とか
! 演算子とか』
「そう。とりあえず ! 演算子を使ってみようか。メンバ関数として作ると
こんな感じになります」
// Data.h
// CDataクラス。
class CData
{
public:
// private メンバ変数。
int m_iData;
public:
// ! 演算子のオーバーロードメンバ関数。
BOOL operator ! ();
};
// Data.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
// ! 演算子のオーバーロードメンバ関数。
BOOL CData::operator ! ()
{
// 有無を言わさずTRUEを返します。
return TRUE;
}
『あ、引数がない』
「使用例はこうなります」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CData クラスの変数を宣言します。
CData cData;
// ! 演算子を使用します。
if( !cData )
{
OutputDebugString( "cData.m_iData == FALSE\n" );
}
// cData.m_iData == FALSE
return 0;
}
「この〈 if( !cData ) 〉が、使用しているところです」
『こうすると、 ! 演算子のオーバーロード、つまり CData クラスの
operator ! () メンバ関数が呼び出されて、絶対に TRUE 返すから』
「 if の中が処理される、という仕組みというわけです」
『引数ないんだね』
「そう。二項演算子の引数のルールってどうだった?」
『えっと……』
・メンバ関数:左オペランドが this 、右オペランドが引数
・普通の関数:左オペランドが第1引数、右オペランドが第2引数
『って感じだった』
「単項演算子の場合には、単純に右オペランドがない、って考えればいいか
ら。メンバ関数版の場合」
『 this がオペランドになるんだね。ってことは、普通の関数版は、
オペランドひとつってこと?』
「そういうこと。普通の関数にすると、以下のようになります」
// Data.h
// CDataクラス。
class CData
{
public:
// private メンバ変数。
int m_iData;
};
// ! 演算子のオーバーロード関数。
BOOL operator ! ( CData &p_rcData );
// Data.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
// ! 演算子のオーバーロード関数。
BOOL operator ! ( CData &p_rcData )
{
// 有無を言わさずTRUEを返します。
return TRUE;
}
『普通に、 CData の参照が引数に追加されただけ……二項演算子の時と同
じだね』
「そう考えると分かりやすいかな。二項演算子のルールが分かっていれば、
単項演算子のルールも分かると思うよ」
『うん、単項演算子の方がオペランド少ないんだし』
「さて。単項演算子にはちょっとやっかいなものがあります。それは
++ 演算子と -- 演算子」
『やっかいなの?』
「 Version 11.20 ( No.220 ) で説明したように、この演算子には前置と
後置の二種類あるんです」
『あ! そういえばそうだった!』
「前置と後置の話は Version 2.7 ( No.018 ) でしたね。前置は普通に増減
するけど、後置の場合は次の行に移らないと増減しません」
『だから後置は使うなって話よね』
「分かりにくいからね。実際、後置の ++ 演算子と -- 演算子は、
オーバーロードが面倒になっています」
『単項演算子で、メンバ関数にしても引数が必要なんだね』
「だから後置の方は作らない方がいいと思います。ただ、前置の方はよく使
うから作れるようにしておいて」
『ほーい』
「あと、憶えておいて欲しいことがあります」
・オペランドのいずれかが、必ずクラスである必要がある
『どういうこと?』
「つまり、こういうのは作れないって事」
// Data.cpp
// int 型に対する + 演算子のオーバーロード。
int operator +( int p_iL, int p_iR )
{
return 0;
}
『これってつまり、 100 + 200 ってことだよね』
「そう、両オペランドが int 型。オペランドにクラスがないと、コンパイル
エラーになります」
error C2803:
'operator +' の宣言で、クラス型のパラメータが 1 つも
指定されていません。
『あらら。ってことはー……つまり、普通の演算子の機能を消して、新しい
機能に書き換える、ってことはできないってこと?』
「そういうこと。演算子のオーバーロードは、あくまでクラスに対して新し
い演算子の使い方を追加する、っていう意味だから」
『元々あるのは変えられない、と。あれ? これは普通の関数だけど、
メンバ関数版の場合は?』
「……メンバ関数版は、左オペランドが?」
『自クラス。あ、メンバ関数版はそれだけでもうクラス使ってるんだ』
「そういうこと。逆に言うと、メンバ関数版なら大丈夫、ってことだね」
『必ずクラスがオペランドになるわけだもんね』
「さて、このように〈オペランドにクラスが必要〉という制限はあるわけだ
けど、逆に言えばそれだけ、ということ」
『どういうこと?』
「たとえば、 API の構造体や、 MFC のクラスといった、既存のクラスに対
して演算子のオーバーロードを行うことができるんです」
『構造体に?』
「たとえば、 Version 7.06 ( No.126 ) で使用した RECT 構造体に、
+ 演算子を使用してみます」
RECT stRect1;
stRect1.left = 100;
stRect1.top = 200;
stRect1.right = 300;
stRect1.bottom = 400;
RECT stRect2;
stRect2.left = 1;
stRect2.top = 2;
stRect2.right = 3;
stRect2.bottom = 4;
// += 演算子を使用してみます。
stRect1 += stRect2;
// コンパイルエラー:
// error C2676: 二項演算子 '+=' :
// 'struct tagRECT' は、この演算子または定義済の演算子に適切な
// 型への変換の定義を行いません。
『うん、できないよね……』
「でも、 += 演算子をオーバーロードしてしまえば……」
// RECT 構造体の += 演算子のオーバーロード。
RECT & operator +=( RECT &p_rstRectL, RECT &p_rstRectR )
{
p_rstRectL.left += p_rstRectR.left;
p_rstRectL.top += p_rstRectR.top;
p_rstRectL.right += p_rstRectR.right;
p_rstRectL.bottom += p_rstRectR.bottom;
return p_rstRectL;
}
『これができちゃう!』
「つまり、演算子のオーバーロードは既存の構造体やクラスにも使えるって
こと。ただ、これをしちゃうと動くものが動かなくなりそうだから、危険な
テクニックかも……」
/*
Preview Next Story!
*/
『演算子のオーバーロードは奥が深い……』
「深すぎるけどね」
『あれ、そんなこと言うなんて意外』
「便利すぎるものほど危険なんだけどね」
『ほほう』
「というわけで次回」
< Version 16.19 型変換演算子のオーバーロード >
『につづく!』
「だからC++はやめられないんだよね」
『ダメじゃん』