Version 17.08
アップキャストとダウンキャスト
「前回は」
『つかありえねー!』
「ということを説明しました」
『だって、』
○基本クラス←派生クラスの代入
×派生クラス←基本クラスの代入
『のはずだったのに、ポインタだと』
○派生クラス←基本クラス←派生クラスの代入
『はできるってことでしょ!? なんで!? 〈基本クラス←派生クラス〉
で m_iData2 の情報が消えちゃうじゃん』
「そこがそもそもの勘違い。普通の代入とポインタの代入は仕組みが違うん
です」
『そうそう、それってどういうこと???』
「代入しているのが、アドレス、っていうことをイメージするのが大事。
前回のプログラムを振り返ってみようか」
// 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
)
{
// CDerivedData クラスを用意します。
CDerivedData cDerivedData;
cDerivedData.m_iData = 100;
cDerivedData.m_iData2 = 200;
// CData クラスのポインタを用意して代入します。
CData *pcData;
pcData = &cDerivedData;
// CDerivedData クラスのポインタを用意して代入します。
CDerivedData *pcDerivedData;
pcDerivedData = (CDerivedData *)pcData;
// 出力します。
char pch[256];
sprintf
( pch
, "%d, %d\n"
, pcDerivedData->m_iData
, pcDerivedData->m_iData2
);
OutputDebugString( pch );
// 100, 200
return 0;
}
「まず、 CDerivedData クラスの cDerivedData 変数を宣言しています」
┌───────────┐
│ CDerivedData │
│ ┌────────┐ │
│ │CData │ │
│ │ │ │
│ │m_iData :100 │ │
│ └────────┘ │
│ m_iData2:200 │
└───────────┘
「で、次の〈基本クラス←派生クラス〉の代入だけど、この時、変数そのも
のが代入されるんじゃなくて、アドレスが代入されるんだ、っていう点が
重要」
『アドレスが代入……?』
「以下の箇所で〈基本クラス←派生クラス〉の代入をしてます」
// CData クラスのポインタを用意して代入します。
CData *pcData;
pcData = &cDerivedData;
「 pcData 変数は、 CData クラスのポインタ。ポインタってことは、
アドレスを入れるための変数だから、 CData クラスの変数じゃないし、
当然 m_iData メンバ変数もないわけ」
『あ……なんかちょっと分かってきたかも』
「図にすると、この代入はこんな感じ」
CDerivedData: 0x00000001
┌───────────┐ ┌───────┐
│ cDerivedData │ │ pcData │
│ ┌────────┐ │ ←←←│ │
│ │CData の部分 │ │ │ 0x00000001 │
│ │ │ │ └───────┘
│ │m_iData :100 │ │
│ └────────┘ │
│ m_iData2:200 │
└───────────┘
『そか、 pcData には cDerivedData のアドレスが入ってるだけなんだね』
「そういうこと。当然、 cDerivedData 変数は CDerivedData クラスのまま」
『? どゆこと?』
「だって pcData 変数は CData クラスだから。本来は CDerivedData クラス
の cDerivedData 変数が、 pcData 変数から見ると CData クラスになる、
ってちょっと変でしょ」
『そう言われるとそうだけど……あ、もしかして pcData って、中にある
〈 CData の部分〉を参照してる、ってこと?』
「そういうこと! 実は、基本クラスのポインタにアドレスを代入すると、
中にある基本クラスの部分をポインタで参照している形になるんです。
だから以下のように出力することもできます」
// 出力します。
char pch[256];
sprintf( pch, "%d\n", pcData->m_iData );
OutputDebugString( pch );
// 100
「この pcData->m_iData は〈 CData の部分〉の中の m_iData を使用して
いるわけです」
CDerivedData: 0x00000001
┌───────────┐ ┌───────┐
│ cDerivedData │ │ pcData │
│ ┌────────┐ │ ←←←│ │
│ │CData の部分 │ │ │ 0x00000001 │
│ │ │ │ └───────┘
│ │m_iData :100 ←←←←←←←←pcData->m_iData
│ └────────┘ │
│ m_iData2:200 │
└───────────┘
『なるほどね、元々の cDerivedData 変数の中にあるのを使ってるわけだ。
あ、質問!』
「はい火美ちゃん」
『 pcData->m_iData2 、ってのは無理?』
「無理です。確かに参照先にはあるんだけど、〈どのメンバが使えるか〉
っていうのは【->】の左側の変数の型に制限されるから」
『この場合だと pcData 変数が CData クラスだから、 CData クラスの
m_iData メンバ変数しか使えないってわけね。それはちょっと面倒』
「さて、ここまでくれば、次の〈派生クラス←基本クラス〉の代入は分かる
かな」
// CDerivedData クラスのポインタを用意して代入します。
CDerivedData *pcDerivedData;
pcDerivedData = (CDerivedData *)pcData;
『これが〈派生クラス←基本クラス〉だけど、それができるのはつまり』
CDerivedData: 0x00000001
┌───────────┐ ┌───────┐
│ cDerivedData │ │ pcDerivedData│
│ ┌────────┐ │ ←←←│ │
│ │CData の部分 │ │ │ 0x00000001 │
│ │ │ │ └───────┘
│ │m_iData :100 │ │
│ └────────┘ │
│ m_iData2:200 │
└───────────┘
『って感じに、元の cDerivedData 変数を参照してるだけだから、ってこと
ね』
「そういうこと! つまり結局は、 cDerivedData 変数がひとつあるだけ
で、そのアドレスをたらい回しにしてるだけなんです」
『そう考えると納得。〈派生クラス←基本クラス〉ができるのも当然ねー』
「まとめると、ポインタの場合、継承関係にある変数への代入はほぼ自由に
できるわけです。で、特に〈派生クラス←基本クラス〉は、元のクラスに戻
す形なら問題なくできるわけです」
『アドレスを入れ替えてるだけだもんね』
「このようにポインタを基本クラスや派生クラスに変換することを以下のよ
うに呼びます」
・基本クラス←派生クラスの代入:アップキャスト
・派生クラス←基本クラスの代入:ダウンキャスト
『アップとダウン、上下ってこと?』
「そう、クラス図を書くとこうなるでしょ」
┌────────┐
│CData │
├────────┤
│m_iData │
├────────┤
│ │
└────────┘
△
│
┌────────┐
│CDerivedData │
├────────┤
│m_iData2 │
├────────┤
│ │
└────────┘
『……まさか、この図の上にキャストするからアップキャスト、下にキャスト
するからダウンキャストってこと?』
「そういうこと。単純でしょ」
『単純というかなんというか……』
/*
Preview Next Story!
*/
『ポインタとそうでないのでこうも違うとは……』
「これが後々重要になってくるんです」
『そなの? そんな感じはしないけど』
「あとでビックリするようなことと関係してくるから」
『び、ビックリ?』
「というわけで次回」
< Version 17.09 継承とオーバーライド >
『につづく!』
「次回はそのビックリのタネの一つ、オーバーライドについて」
『オーバーロードの親戚?』
「実は赤の他人」