Version 16.20
オーバーロードの選択
「まず、前回問題になった点を洗い出してみます」
『コンストラクタか、型変換演算子のオーバーロードか、ってことだったよね』
「そう、この選択は非常に重要です。まず、実際に例を見てみようか。
// Data.h
class CData;
class CData2
{
public:
// public メンバ変数。
int m_i;
// 引数のないコンストラクタ。
CData2(){}
};
// CDataクラス。
class CData
{
public:
// public メンバ変数。
int m_iData;
// CData2クラスへの型変換演算子のオーバーロード。
operator CData2();
};
// Data.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
// CData2クラスへの型変換演算子のオーバーロード。
CData::operator CData2()
{
return CData2();
}
「ちょっと手抜きして、CData2クラスのメンバ関数はCData2クラス内で定義
しています」
『まぁ何もしてないからいいんじゃない?』
「この例では、CDataクラスにCData2クラスへの、型変換演算子の
オーバーロードを行っています」
『〈CData::operator CData2()〉がそうね』
「この時の使用例は、以下のようになります」
// 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.m_iData = 100;
// 型変換演算子のオーバーロードを呼び出します。
CData2 cData2 = cData;
// CData::operator CData2()
return 0;
}
「 cData 変数は CData クラスのため、 CData2 クラスにキャストが行われ
るので CData クラスの operator CData2() メンバ関数が呼び出されます」
『型変換演算子のオーバーロード関数が呼び出される、ってわけね』
「さて、この状態で、 CData クラスを持つコンストラクタを CData2 クラス
に追加してみます」
// Data.h
class CData;
class CData2
{
public:
// public メンバ変数。
int m_i;
// 引数のないコンストラクタ。
CData2(){}
// CData クラスを引数に持つコンストラクタ。
CData2( CData &p_rcData )
{
OutputDebugString( "CData2( CData &p_rcData )\n" );
}
};
// CData クラスは同じです。
// Data.cpp ファイルも同じです。
『 CData2 クラスに、 CData クラスが引数のコンストラクタ追加したのね』
「この状態で、先ほどの例を試してみます」
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
CData cData;
cData.m_iData = 100;
// どっちが使われる?
CData2 cData2 = cData;
// CData2( CData &p_rcData )
return 0;
}
『あ! 今度はコンストラクタの方が呼び出された!』
「このように、型変換演算子のオーバーロードではなく、コンストラクタの
オーバーロードの方が呼び出された、というわけです」
『そうなんだ……それじゃ、型変換演算子の方は必要ない? あ、でも』
CData2 cData2 = (CData2)cData;
『とかやったら必要かな?』
「……実は必要じゃないんです。この時も、コンストラクタが呼び出されま
す」
『??? それはないでしょ、だってキャストなんだし』
「実は、キャスト時に、コンストラクタが呼び出される場合があるんです」
『……何それ、ちょっと待って』
「たとえば、以下のように、 cData 変数を CData2 クラスにキャストして、
そのメンバ変数の m_i を使用した場合」
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
CData cData;
cData.m_iData = 100;
// どっちが使われる?
int i = ( (CData2)cData ).m_i;
// CData2( CData &p_rcData )
return 0;
}
『げげげげげ!! なにそれ、コンストラクタ呼び出されてる!!
型変換演算子の方呼ばれてないし!』
「実は、キャスト時には〈仮の変数〉を作るという機能があるんです」
『仮の変数??』
「たとえば、 int 型と double 型」
『 double 型って、 Version 12.03 ( No.226 ) でやった浮動小数点って
ゆーのだよね。あれは複雑だった……』
「仮数部とか実数部とか説明したよね。 double 型は int 型と全然構造が
違うっていうことを思い出して」
『うん、思い出した』
「それを踏まえて……」
int i = 100;
double d = (double)i;
「という式を考えてみて。 int 型の i を、 double 型にキャストしていま
す」
『うん、キャストしてる』
「キャストする結果、〈 (double)i 〉は、内部では i の値 100 を double
型に変換する、という処理が行われます」
『うん、全然中身が違うんだから、新しく作り直し……待って』
「そう、〈 (double)i 〉を行うと、 double 型の変数が新しく作られて、
その中に変換後の 100.0 が格納される、というわけです」
『新しく作られる! 変数が作られる、コンストラクタが呼び出される』
「ということ。この例で無理矢理例えるなら、〈 double::double( int ) 〉
っていうコンストラクタが呼び出されるわけです」
『うわー、キャストでコンストラクタが呼び出されちゃったー!!』
「このように、コンストラクタがキャスト代わりになってしまう場合がある
わけです」
『型変換演算子のオーバーロードの意味がない……』
「でも、もちろんコンストラクタが無ければ意味はあるよ」
『あ、そういえば』
「そして、重要な点は、コンストラクタは【変換先】になきゃいけないけど
型変換演算子のオーバーロードは【変換元】にあればいい、ということ」
『あ、そっか、元々あるクラスだったらコンストラクタ追加できないし』
「そういうこと。そういう使い分けはできるかな」
『でも難しそう……』
「実際、演算子のオーバーロードはとても難しいよ。今の話はコンストラクタ
だけど、 = 演算子のオーバーロードは関係ないでしょ」
『げ、そっか、そっちはキャストしたときには呼ばれないよね』
「でも、コンストラクタと = 演算子のオーバーロードは同じようにしない
と」
『初期化と代入が違っちゃう!』
「他にも、普通の関数にするのかメンバ関数にするのかとか」
『それもあった……』
「というわけで、演算子のオーバーロードはとても面倒なのです」
/*
Preview Next Story!
*/
『って、そんなもん教えるなー!』
「だったらいいんだけど、使えないと困る場面があるんだよね」
『げ、そうなの?』
「そういう典型を次回は説明します」
『うーん教わりたくない』
「というわけで次回」
< Version 16.21 演算子のオーバーロードの実例 >
『につづく!』
「それ呼んだ後は STL & iostream 入門をどうぞ」
『宣伝!? それよりそれも解説してよー』
「それだけで何年かかるか……」