Version 17.29
継承とコンストラクタ
「前回まではスマートポインタの説明をしてきました」
『なんかすっごく複雑だったんだけど』
「まぁ、あれが一番難しい部分って言ってもいいからね……さて、今回から
は、継承関係で憶えておかなきゃいけない点を説明します」
『はーい』
「まずは、継承を使う時の、コンストラクタの注意点について」
『えー? コンストラクタってただでさえ複雑なのに、まだ難しいことある
の?』
「コンストラクタはかなり複雑なんだよね……まず、単純にコンストラクタ
を各クラスで作る例から」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// 基本クラス。
class CBase
{
public:
// コンストラクタ。
CBase()
{
OutputDebugString( "CBase::CBase()\n" );
}
};
// 派生クラス。
class CDerived : public CBase
{
public:
// コンストラクタ。
CDerived()
{
OutputDebugString( "CDerived::CDerived()\n" );
}
};
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDerived クラスの変数を作ります。
CDerived cDerived;
// CBase::CBase()
// CDerived::CDerived()
return 0;
}
「このプログラムでは、 CBase クラスが基本クラス、 CDerived クラスが
派生クラスです」
『両方ともコンストラクタ持ってるね』
「ここで、 CDerived クラスの変数を作ります」
// CDerived クラスの変数を作ります。
CDerived cDerived;
// CBase::CBase()
// CDerived::CDerived()
「するとこのように、まず基本クラスのコンストラクタが呼び出されて、
次に派生クラスのコンストラクタが呼び出されます」
『ホントだ。これって順番決まってるの?』
「うん、決まってるんです。なぜかっていうと、派生クラスは基本クラスを
使えないといけないから。たとえば、 CBase クラスに m_i メンバ変数を
追加したとします」
// 基本クラス。
class CBase
{
protected:
int m_i;
「このメンバ変数は、 CDerived クラスからは見えているわけだから、当然
CDerived クラスのコンストラクタから使えなきゃいけないんです」
// コンストラクタ。
CDerived()
{
// 基本クラスのメンバ変数にアクセスします。
m_i = 100;
}
『そか、派生クラスから基本クラスは使えるんだもんね』
「だから、派生クラスのコンストラクタが呼び出される時には基本クラスは
使える状態でなきゃいけない、それはつまり」
『コンストラクタが呼ばれてなきゃいけない!』
「というわけです。だから、必ず基本クラスが先、派生クラスが後になるわ
けです」
『なるほどねー』
「さて、次は〈コンストラクタは派生クラスに引き継がれない〉という話」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// 基本クラス。
class CBase
{
int m_i;
public:
// 引数を持つコンストラクタ。
CBase( int p_i )
: m_i( p_i )
{
OutputDebugString( "CBase::CBase()\n" );
}
};
// 派生クラス。
class CDerived : public CBase
{
// 空。
};
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDerived クラスの変数を作ります。
CDerived cDerived( 100 );
// コンパイルエラー:
// error C2440: (略)
return 0;
}
「このプログラムはコンパイルエラーになります」
『げげ、ホントだ! なんで?』
「まず、 CBase クラスには引数を持つコンストラクタがあります」
// 引数を持つコンストラクタ。
CBase( int p_i )
「これを使おうと、 CDerived クラスの変数を作るときに以下のように値を
渡しています」
CDerived cDerived( 100 );
『うん、できそうだと思うけど』
「ところが、コンストラクタは派生クラスには引き継がれないんです。なの
で、 CDerived クラスには CBase クラスの引数を持つコンストラクタが存在
しないことになるんです」
『げ、つまり』
// 派生クラス。
class CDerived : public CBase
{
// 空。
};
『は本当に空、 CBase クラスの引数を持つコンストラクタも持ってないこと
になるわけね』
「そのため、引数を持つコンストラクタを派生クラスでも使いたい場合には、
同じように派生クラスでも引数を持つコンストラクタを作る必要があります。
たとえば以下のように」
// 派生クラス。
class CDerived : public CBase
{
public:
// 引数を持つコンストラクタ。
CDerived( int p_i )
: CBase( p_i ) // ←基本クラスのコンストラクタを呼び出します。
{
OutputDebugString( "CBase::CBase()\n" );
}
};
「まず、 CDerived クラスにも同じように引数を持つコンストラクタを作り
ます」
// 引数を持つコンストラクタ。
CDerived( int p_i )
「次に、このコンストラクタから CBase クラスの引数を持つコンストラクタ
を呼び出します」
『コンストラクタからコンストラクタを呼び出すの!?』
「そう、派生クラスのコンストラクタから基本クラスのコンストラクタを
呼び出すことができるんです。呼び出し方はこんな感じ」
// 引数を持つコンストラクタ。
CDerived( int p_i )
: CBase( p_i ) // ←基本クラスのコンストラクタを呼び出します。
「この〈: CBase( p_i )〉の部分がそう」
『これって、コンストラクタでメンバ変数を初期化するのと同じだね』
「そう、それと同じように〈: 基本クラス名( 引数 )〉って書けばOK」
『これ書かないとどうなるの?』
「引数なしのコンストラクタが呼ばれます」
『んー、この例だとデフォルトコンストラクタが呼ばれるってことね』
「呼ばれないよ。 Version 16.11 ( No.338 ) で説明したように、引数を持
つコンストラクタがあると」
『あ”、デフォルトコンストラクタがなくなるんだ』
「そういうこと。だからこの例ではこうやって基本クラスの引数を持つ
コンストラクタを呼び出さなきゃいけないんです」
『それをしないとコンパイルエラーになる、と』
「あと、この書き方を使えば、基本クラスのコンストラクタがオーバーロード
されていても、どのコンストラクタを使うのか選択できるからね」
『渡す引数でコンストラクタを変えられるってこと?』
「そういうこと。そういう意味でも、この使い方は重要かな」
/*
Preview Next Story!
*/
『なんでコンストラクタってこんな複雑なの!?』
「 C++ はクラスが普通の変数だからね……」
『え? それは当たり前だと思うけど』
「当たり前なんだけど、だから複雑になるというか」
『???』
「というわけで次回」
< Version 17.30 デストラクタを仮想関数にする >
『につづく!』
「ま、他の言語の話はいっか」
『?????』