Version 16.03
this ポインタ
「前回はメンバ変数とメンバ関数の作成をしてみました」
『だけど、肝心な所はぐらかされちゃったんだけど』
「そう、なぜメンバ関数はクラス型変数を使用しないと呼び出せないのか」
// クラス型変数を使わずに
// メンバ関数を呼び出します。
CData.Output();
// コンパイルエラー:
// error C2143: 構文エラー : ';' が '.' の前に必要です。
CData::Output();
// コンパイルエラー:
// 'CData::Output' :
// 静的でないメンバ関数の中で呼び出しが正しくありません。
『っていう形じゃ呼び出せなくて』
// メンバ関数を呼び出します。
cData.Output();
『ならいい、って考えると、メンバ関数ってクラス型変数の中にあるような
気がするんだけど』
「でも、メンバ関数に限らず関数は変数の中に入れられません」
『だよね、関数ポインタとかに使えるくらいで変数の中には入れられないん
だから。ってことはどういうことなの?』
「まず、メンバ関数の中ではメンバ変数にアクセスできなければなりませ
ん」
『だよね』
「そのために、そのメンバ変数が入っている、クラス型変数と一緒に使う必
要があります」
『その辺は分かるんだけど……でもそれじゃクラスの中にメンバ関数が入っ
てないことにならないし』
「そう、つまりどうクラス型変数とメンバ関数が結びついているか。実は、
メンバ関数には〈隠された引数〉があるんです」
『隠された引数?』
「そう。それは、自クラスのポインタ。全てのメンバ関数は、自クラスの
ポインタを引数として隠し持っているんです。たとえば」
// CDataクラスのOutput()メンバ関数の定義。
void CData::Output()
{
// 出力します。
char pch[256];
sprintf( pch, "%d\n", m_iValue );
OutputDebugString( pch );
// 100
}
「というメンバ関数は」
// CDataクラスのOutput()メンバ関数の定義、の仮の姿。
void Output( CData *p_pcData )
{
// 出力します。
char pch[256];
sprintf( pch, "%d\n", p_pcData->m_iValue );
OutputDebugString( pch );
// 100
}
「という関数になります」
『お、 CData *p_pcData が増えた!』
「この引数が〈隠された引数〉。この渡されたポインタを通してメンバ変数
にアクセスします」
『 p_pcData->m_iValue ってしてるとこがそうだね』
「これなら、メンバ変数にアクセスできます。呼び出す側は」
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDataクラスの変数を宣言します。
CData cData;
// m_iValueに値を入れます。
cData.m_iValue = 100;
// メンバ関数を呼び出します。
cData.Output();
return 0;
}
「が」
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDataクラスの変数を宣言します。
CData cData;
// m_iValueに値を入れます。
cData.m_iValue = 100;
// メンバ関数もどきを呼び出します。
Output( &cData );
return 0;
}
「に置き換わった感じかな」
『こんなふうに、裏では引数に渡されてるってこと?』
「そういうこと。これならメンバ変数にアクセスできるでしょ」
『確かに』
「こういう置き換えを、メンバ関数がコンパイルされるときにしている、
ってことなんです」
『隠された引数があって、そこにクラス型変数のポインタを渡す、と』
「これには、歴史が関係しているんです」
『れ、歴史?』
「元々、C言語にはメンバ関数がありませんでした。それが、C++言語になっ
てメンバ関数が追加されました」
『つまり、元々関数があって、それを拡張したのがメンバ関数、ってこと?』
「そういうこと。メンバ関数はあくまで関数。関数でメンバ変数を使用でき
るようにするため、メンバ関数には隠された引数を持たせ、〈クラス型変数
.メンバ関数()〉としたらクラス型変数のアドレスが渡される、という仕様
にしたわけです」
『だからメンバ関数の中でメンバ変数が使えるし、そのための引数に渡す必
要があるから、クラス型変数と一緒にメンバ関数を呼び出さなきゃいけない
と』
「これは、あとあと重要になるから、このイメージはしっかり憶えておい
て」
『重要なの?』
「たとえば、ウィンドウプロシージャ」
『ウィンドウプロシージャ? なんでここで』
「ウィンドウプロシージャって、関数ポインタを渡すでしょ。でも、その
関数ポインタにメンバ関数のポインタは渡せないんです」
『あ、そうなんだ、試したことなかったけど』
「その理由もこの辺にあるから。もう少し先に説明するけどね」
『そか、なんとなく分かるかも……』
「さて、クラス型変数がポインタとして引数に渡される、という説明をしま
した。実は、この引数、ちゃんと存在します」
『え、そうなの? 存在しない引数にアクセスできちゃうの?』
「できちゃうんです。隠された引数は、必ず変数名が【this】になります」
『じす?』
「そう、 this 、〈これ〉。メンバ関数の中ではこの this という変数に
アクセスできます」
// CDataクラスのOutput()メンバ関数の定義。
void CData::Output()
{
// thisポインタを通してメンバ変数にアクセスします。
int iValue = this->m_iValue;
// 出力します。
char pch[256];
sprintf( pch, "%d\n", iValue );
OutputDebugString( pch );
// 100
}
『うわ、本当にアクセスしてる!』
「メンバ関数の中では、常に this 変数を使用することができます。この
変数の型は、クラスのポインタになります」
『この例だと CData * だね』
「そういうこと。 this には、呼び出し元のクラス型変数のアドレス、つま
り &cData が格納されています」
『だから、メンバ変数にアクセスできるってわけね』
「重要なのは、こうしてプログラム上でアクセスできるけど、プログラムで
使わなくても、裏では使っている、ってこと」
『あー、そういえば隠れた引数なんだもんね』
「そう、だから」
// CDataクラスのOutput()メンバ関数の定義。
void CData::Output()
{
// 出力します。
char pch[256];
sprintf( pch, "%d\n", m_iValue );
OutputDebugString( pch );
// 100
}
「は、コンパイル時には」
// CDataクラスのOutput()メンバ関数の定義。
void CData::Output()
{
// 出力します。
char pch[256];
sprintf( pch, "%d\n", this->m_iValue );
OutputDebugString( pch );
// 100
}
「に変換されている、というわけです」
『あ……そっか、メンバ関数の中でメンバ変数にアクセスできるのは、
this を通して呼び出し元のクラス型変数にアクセスしてるから……
ってことなんだ』
「そういうこと! 重要な点は、まず隠された引数を通して、呼び出し元の
クラス型変数のポインタが渡され、それは this ポインタとして使うことが
できて、その this ポインタを通してメンバ変数にアクセスしてる、という
こと」
『それを、コンパイルするときに自動的にしてくれてる、ってことね』
/*
Preview Next Story!
*/
『あ、まだ public とか private って教わってないような』
「そういえばそうだね。次回はそれにしようか」
『あれ? でも教わってるような気も』
「どっち……」
『というわけで次回』
< Version 16.04 メンバのスコープ >
「につづく!」
『まぁ二度教えればいいじゃん』
「そういうもんだいでは」
『コピペでいいんだし』
「そういう問題でも……」