Version 16.10
コンストラクタの復習
「さて、今回はコンストラクタについて説明します」
『ちょっ、まっ!!』
「?」
『前回の、前回の!』
「…… const メンバ関数については、慣れやノウハウでなんとかするしか
ないかも。当分は const ポインタや const 参照は一部でしか使わないよう
にして、 const メンバ関数は getter 等の分かりやすいものにだけ付ける
とか」
『うわぁ消極的』
「いや実際、 const メンバ関数はきっと C++言語の中で一番難しいと思う
よ」
『一番難しい……?』
「そ、実践で使うには仕様が複雑だし、単純なルールとか作るの難しいから
ね。だから、当面は深入りしない方がいいかも」
『でも、 const ポインタとか使えないのはちょっとね……』
「そうだね、作るのは深入りしなくても、 MFC の使用例を参考に、どうい
う風にすればいいのか実例を見ていけば、いつか」
『使えるようになる?』
「なるよきっと」
『それまでの辛抱ってことね』
「さて、今回はコンストラクタについて復習します」
『コンストラクタも何度か出てきたね』
「 Version 7.08 ( No.128 ) 、 Version 11.11 ( No.211 ) 、
Version 11.17 ( No.217 ) 、 Version 11.18 ( No.218 ) で説明したね」
『よーするに、クラスの変数を作るときに呼ぶ関数なんだよね』
「そう。たとえばこんな感じ」
// Data.h
// CDataクラス。
class CData
{
private:
// private メンバ変数。
int m_iData;
public:
// コンストラクタ。
CData();
// m_iData の getter 。
int GetData() const;
};
// Data.cpp
#include <Windows.h>
#include <stdio.h>
#include "Data.h"
// コンストラクタ。
CData::CData()
{
m_iData = 0;
}
// m_iData の getter 。
int CData::GetData() const
{
return m_iData;
}
「コンストラクタは、クラス名と同じ名前のメンバ関数です」
// コンストラクタ。
CData();
「戻り値がない点に注意してください」
『戻り値があっても受け取れないもんね』
「コンストラクタでは、主にメンバ変数の初期化を行います」
// コンストラクタ。
CData::CData()
{
m_iData = 0;
}
「このように、メンバ変数に初期値をセットするのがコンストラクタの役割
です」
『あれ、もうひとつ別の書き方があるんじゃなかったっけ』
「そう。実は、この例は本当は間違い。メンバ変数をちゃんと初期化する場
合には、以下のようにします」
// コンストラクタ。
CData::CData()
: m_iData( 0 )
{
}
『相変わらず見慣れない書き方……』
「メンバ変数の〈本当の初期化〉は、「{」の直前に行います。これはなぜ
かというと、〈処理の前に初期化が必要だから〉です」
『処理の前に初期化?』
「コンストラクタの {} の中では、計算とか、関数を呼んだりといったこと
ができてしまいます。メンバ変数の初期化は、そういったことの前にしてお
かないといけません」
『なるほど、初期化してない変数で計算しちゃまずいもんね』
「特に、メンバ変数が const の場合は、この方法でないと初期化できませ
ん」
// private メンバ変数。
const int m_iData;
「const な変数は、変数を宣言したと同時に値をセットする、つまり初期化
時に値をセットしなければいけません」
『 {} の中に入っちゃったら、もう変数が作られちゃってるからそれじゃダメ
ってわけね』
「そういうこと。だから、 const 変数の場合にはこのように { の前で初期化
する必要がある、というわけです」
『 () で初期化するのは Version 11.18 ( No.218 ) で教わった方法だね』
「そう、変数は = だけじゃなく () で関数のように初期化できるんだけど、
このコンストラクタの初期化時には () で初期化しないとダメだから」
『なんで?』
「うーん、これはなんでだろう……うーん……」
『はいはい。じゃあ次』
「……使用例としては、こうなります」
// 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;
// GetData()メンバ関数を呼びだします。
int i = cData.GetData();
char pch[256];
sprintf( pch, "%d\n", i );
OutputDebugString( pch );
// 0
return 0;
}
「コンストラクタは、変数を宣言したときに呼びだされます」
// CData クラスの変数を宣言します。
// ここでコンストラクタが呼びだされます。
CData cData;
「この時にコンストラクタ、つまりCDataクラスのCDataメンバ関数が呼びだ
される、というわけです」
『質問!』
「はい火美ちゃん」
『 CData クラスに SetData() メンバ関数作って、外から初期化する、って
いうのはだめなの?』
「基本的にダメ。 Version 16.05 ( No.332 ) で【カプセル化】について説
明したでしょ」
『うん、メンバ変数を private にして、 getter と setter を作るってゆー
のだよね』
「そうそれ。カプセル化の考え方っていうのは〈自分のことは自分で面倒を
見る〉という考え方なんです」
『???』
「メンバ変数を private にして getter と setter を作ることで、クラス
の中を〈閉じた空間〉にすることができます」
『うん、メンバ関数経由でしかアクセスできないもんね』
「こうするのは、 CData クラスについて一番適切に処理できるのは CData
クラス自身、だから実際にメンバ変数へとアクセスするのは自分だけ、だか
らです」
『うん』
「だから、自分自身の初期化も自分でするわけです。初期値は、もしかした
ら 0 じゃなく他の値の方がいいかもしれないし、そういったことを考えて
最も適切な初期化ができるのは自分自身だけだから」
『うーん、なんとなく分かるような……』
「分かりにくいのは、これが一種の〈思想〉だからなんだと思うよ」
『しそー?』
「考え方、クラスに……なんて言うのかな、人権?」
『クラス権?』
「そういうものを与える、っていう考え方なんだよね。たとえばクラスが
DLL 単体で提供されていたら、プログラムは見えないでしょ」
『うん、見えないね』
「そのクラスは完全に閉じた存在、ブラックボックスになっていて public
メンバ関数経由でしかアクセスできないわけだ」
『それがカプセル化……』
「中でなにやってるか分からないけど、それは気にしない、クラスの自主性
に任せる、っていうのがカプセル化の考え方」
『そのクラスのプログラムが間違っていたら?』
「使わない」
『む……』
「まぁ、そういう融通の利かない部分があるからこそ、クラスってとっつき
にくい所があると思うから、そういった部分をどう理解するかもこれからの
問題かも」
/*
Preview Next Story!
*/
『なんかしそーとか言われると難しいかも』
「言語仕様の裏にある考え方を理解できるといいんだけど」
『そういうのって必要?』
「必須じゃないけどあるといいかな」
『んー』
「というわけで次回」
< Version 16.11 様々なコンストラクタ >
『につづく!』
「じゃあこう考えて、 C++言語を作った人の気持ちになる」
『なんかよけいわからん……』