Version 17.31
継承のまとめ
「さて、今回で継承については終わりです」
『おお! かなり複雑な話だったね……』
「だね、最初から振り返ってみようか」
『はーい』
「まず Version 17.01 ( No.356 ) で、継承の説明をしました」
『あるクラスにメンバ関数やメンバ変数を追加していく事ができる――って
いうより、クラスにクラスを被せる感じ?』
「そうだね、単純に考えれば、派生クラスは〈基本クラスのメンバを持ち、
それに新たなメンバを加えたクラス〉ってことになります。でも実際には」
『Version 17.03 ( No.358 ) みたいに基本クラスを派生クラスが包んでる
わけね』
「だからこそ、 protected っていう指定があるわけです。アクセス修飾子
は Version 17.04 ( No.359 ) でまとめているからちゃんと憶えてね」
『次は……代入関係だね』
「 Version 17.06 ( No.361 ) から説明しているように、代入には普通の
代入とポインタでの代入とがあって、まずこのふたつが全然違うものだって
ことを憶えておいて」
『普通の代入だと、コピーコンストラクタが呼び出されてメンバ変数が
コピーされるけど、ポインタの代入だとアドレスがコピーされるだけ、ね』
「だから、ポインタの代入では、アドレスが指している先の変数の型は変わ
らないわけです。それが Version 17.08 ( No.363 ) です」
『見た目の型と実際の型が違うっていうのが不思議よね。それが
ポリモーフィズムってことなんだよね』
「そういうことだね。ポリモーフィズム、つまり違う型のように動作するに
はオーバーライドが必要。それが Version 17.09 ( No.364 ) や、あと
Version 17.10 ( No.365 ) の仮想関数」
『結局、仮想関数にしないとオーバーライドは意味がないんだよね』
「そうなるね。だから実際には、継承する気があるならメンバ関数は全部
仮想関数にした方がいいかも」
『そうすれば必ずポリモーフィズムできるもんね』
「ただ……ここから本題だけど、 Version 17.12 ( No.367 ) で説明してい
るポリモーフィズムがちゃんと機能するためには、あくまで変数をポインタ
で扱っていないといけません」
『そうしないとアップキャストできないし仮想関数も意味ないもんね』
「そういうこと。で、実際の所、ポインタで処理をする場合、っていうのは
あまりないんです」
『そういわれるとそうだね……わざわざポインタを使う必要ってないような
気がする』
「だから、普段はポリモーフィズムを意識する必要はないってこと」
『……??? なんかそれって変くない? 使わなきゃいけないから教えた
んでしょ?』
「もちろん。 Version 17.16 ( No.371 ) や Version 17.17 ( No.372 ) で
説明したように、ポリモーフィズムの最大のメリットは、関数ポインタの
代わりとしての使い方です」
『そうそう、〈呼び出してもらう〉っていう場合に使うんだよね』
「だから、そういうプログラムを組む、っていう時にポリモーフィズムを使
う、継承・仮想関数・オーバーライドを使うって考えればいいかな」
『それが目的から考えた場合、ってことだよね』
「そう。今回の章では、目的よりもまずポリモーフィズムの機能から説明し
たからその辺がわかりにくかったかもね」
『うん、なんのために、っていうのが多かったかも』
「ただ、ポリモーフィズムは仕組みそのものがかなり複雑だから、まずはそ
こからっていうのが今回の方針。あと、 C++ 言語の一番難しい部分を見て
もらう、っていうのも目的」
『あー……』
「使う上でポインタが必要、で、 new について Version 17.21 ( No.376 )
で説明して、さらにスマートポインタについて Version 17.23 ( No.378 )
で説明しました」
『これが一番難しい所、ってことね。ここが理解できればいい、ってことで
いいのかな』
「そうなるけど……難しい理由のひとつは、見えないところで動いている
処理を知らなきゃいけない、っていう点かな」
『だよね。コピーコンストラクタや、戻り値で返される時に作られる変数と
か、なんかすごく複雑』
「しかも、そういう部分は本にはあまり書かれていない話だからね」
『なんで書いてないの?』
「難しいからっていうのもあるし、〈見えないところで動いている〉ってこ
とは、好意的に解釈すれば、〈便利に使える〉ためにそういう機能があるっ
てことだから」
『分からなくても動いていればいい?』
「ってことになるね。もちろん、スマートポインタみたいなものを作る時に
は知らなきゃいけないけどね」
『そのレベルまで行くかどうか、ってことね』
「あとは Version 17.29 ( No.384 ) 以降でコンストラクタやデストラクタ
関係の説明をしました」
『これで継承関係は終わり、ってことね』
「でも継承関係でまだ説明してない部分はあるんです」
『げ! まだあるんだ……』
「結構ね。次章で、実践を交えてそのあたりを説明していこうと思います。
と、あと最後にちょっと考え方の話」
『ほい』
「まずは一番基本的な継承から」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// 基本クラス。
class CBase
{
public:
// 仮想関数。
virtual void Output1()
{
OutputDebugString( "CBase::Output1()\n" );
}
};
// 派生クラス。
class CDerived : public CBase
{
public:
// 仮想関数(オーバーライド)。
virtual void Output1()
{
OutputDebugString( "CDerived::Output1()\n" );
}
// 上のとは別のメンバ関数。
virtual void Output2()
{
OutputDebugString( "CDerived::Output2()\n" );
}
};
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDerived クラスの変数を作ります。
CDerived cDerived;
cDerived.Output1();
// CDerived::Output1()
cDerived.Output2();
// CDerived::Output2()
// アップキャストします。
CBase *pcBase = &cDerived;
// ポリモーフィズムします。
pcBase->Output1();
// CDerived::Output1()
// pcBase->Output2(); ←こっちは呼べません。
return 0;
}
「 CBase クラスと CDerived クラスがあります。 CDerived クラスは
CBase クラスから継承しているので、 CBase クラスが基本クラス、
CDerived クラスが派生クラスになります。図にするとこうなります」
┌────────┐
│CBase │
├────────┤
│ │
├────────┤
│Output1() │
└────────┘
△
│
┌────────┐
│CDerived │
├────────┤
│ │
├────────┤
│Output1() │
│Output2() │
└────────┘
「 Output1() メンバ関数はオーバーライドしています」
『だからアップキャストして呼び出すと、派生クラスの方が呼び出せるわけ
ね』
// アップキャストします。
CBase *pcBase = &cDerived;
// ポリモーフィズムします。
pcBase->Output1();
「そう、この pcBase ポインタは cDerived 変数を指しているっていうこ
と、仮想関数はオーバーライドした方が呼ばれるっていうことを忘れない
で」
┌ cDerived 変数────┐ ┌ pcBase ポインタ──┐
│ CDerived │←←│ 0x00000001 │
│ ┌────────┐ │ └──────────┘
│ │CBase │ │
│ │ │ │
│ │Output1()→ │ │
│ └─────↓──┘ │
│ Output1()← │
│ Output2() │
└───────────┘
アドレス:0x00000001
「このように、 pcBase ポインタを通して呼び出しているのは、
cDerived 変数の Output1() メンバ関数。これは仮想関数で、オーバーライド
されているから、 CDerived クラスの方が呼び出されます」
『 CBase クラスのを呼び出しているように見えて、実は CDerived クラス
の方、これがポリモーフィズムってことね』
「さて、ここで重要なのは、このようにアップキャストした場合、実際の
クラスが CDerived クラスだとしても、その CDerived クラスの
Output2() メンバ関数は呼べない、ってこと」
// pcBase->Output2(); ←こっちは呼べません。
『そうなんだよね、どのメンバ関数が呼べるか、はこの左側の変数の型、
つまり pcBase ポインタの型で決まるんだよね。実際の型じゃなくて』
「これは、アップキャストをすることで〈 CDerived クラスのうち、
CBase クラスのメンバだけ呼び出せるようにした〉ということなんです」
『???』
「 cDerived 変数は CDerived クラスだけど、アップキャストすることで
CBase クラスのメンバ関数・メンバ変数だけアクセスできるようになる、と
いうこと」
// アップキャストします。
CBase *pcBase = &cDerived; ← pcBase を通しては
Output2() メンバ関数は呼べない
『……できることを少なくしてる、ってこと? でもなんで?』
「実はメリットがあるんです。こういった視点を持ちつつ最終章へ!」
/*
Preview Next Story!
*/
『さ……最終章!?』
「というわけで次回」
< Version 18.01 ウィンドウプログラムをクラスで作る >
『につづく!』
「次回は応用編でーす」
『説明しろー!!』