Version 11.11
コンストラクタとデストラクタ
「前回は、色々な API を使ってメモリ確保してみました」
『3種類くらいあるんだよね。それで、組み合わせ変えちゃったらまずいん
だよね』
「そう、確保する関数と解放する関数はペアになっていて、それを間違える
とエラーになっちゃいます」
『つまりそれはかなり注意してしなきゃいけないってことね』
「でも、難しい部分もあるんです」
『難しい?』
「 malloc() とかを使って取得したポインタって、長く使う事があるんで
す。たとえば戻りとして返したりとか」
char *ReturnPointer( const char *p_pch )
{
return (char *)malloc( strlen( p_pch ) );
}
『文字列受け取って、その長さ分だけ確保して返す、って関数だね』
「この関数から返されたポインタは当然解放しなきゃいけないんだけど、で
も、この関数の中を見ないと何で解放すればいいのかわからないでしょ」
『つまり、こうやって関数見て、 malloc() で確保してるってわからないと
free() で解放すればいい、ってわからないってこと?』
「そういうこと。このポインタだけ処理するならいいんだけど、こんな似た
ような関数がいっぱいあって、それぞれで別の関数で確保してたら」
『どのポインタがどれで解放したかってわからない、と』
「で、こういう時にはクラスの【コンストラクタ】と【デストラクタ】とい
う機能を使います」
『コンストラクタってやったよね、前に』
「 Version 7.08 ( No.128 ) でね。コンストラクタは変数が作られた時に
呼ばれる関数がコンストラクタ。で、変数がなくなるときに呼ばれるのが
デストラクタ」
『変数がなくなるとき?』
「関数から出るときに変数がなくなるでしょ」
『……質問!』
「はい火美ちゃん」
『さっきの例を見ると、 malloc() とかで確保したポインタはなくならない
ってこと?』
「そういうこと。言い換えると、普通の変数は自動で作られて自動で解放さ
れるってことかな」
『そういうことになるのか……』
「たとえばね」
char *ReturnPointer2( const char *p_pch )
{
char *pchRet = (char *)malloc( strlen( p_pch ) );
return pchRet;
}
「ってした場合。 pchRet は普通の変数だから、この関数から抜けるとなく
なります」
『でも malloc() で確保した方はなくならない、と』
「 pchRet はアドレスを入れる変数ってだけで、そのアドレスが指すメモリ
領域とは無関係だからね」
『なんとなくわかる……と思う』
「 malloc() だから難しいのかな」
void DoublePointer()
{
char pch[128];
char *pchRet = pch;
}
『あ、 malloc() じゃなくて普通の変数になった』
「この関数から抜けたら pch はなくなるでしょ。もし pchRet がなくなる
ときに、そのポインタが指してる pch もなくなしちゃうとしたら」
『二重でなくなっちゃうからあり得ないってことね』
「そういうこと。で、この pch の方は普通の変数だからなくなるでしょ、
malloc() と違って」
『うん』
「ってことは、この〈なくなるとき〉にデストラクタが呼ばれるってこと。
実際にそれを CString で確認してみます」
void CreateCString()
{
CString str = "あいうえお";
}
『ただ CString 作るだけだね』
「この作る所でブレークポイント置いて」
『ほい』
「で、実行してそこで止めてから、ステップインするとコンストラクタに入
れるから」
『あ、そうやれば入れるんだ』
「コンストラクタはこんな関数になってます」
CString::CString(LPCTSTR lpsz)
{
// 略。
}
『懐かしい、クラスのメンバ関数だね』
「コンストラクタは普通のメンバ関数と違って、戻り値がなくて、関数名が
クラス名と同じです」
『あ、ホントだ』
「 MFC のコードだからそのまま載せられないけど、中で AllocBuffer()
ってメンバ関数を呼び出してるでしょ。これでメモリを確保してます」
『確保ってなにでしてるの? GlobalAlloc() とか?』
「ううん、次回教える new っていうのを使ってます」
『そういえばランタイムと API の他にそういうのもあったね』
「ランタイムや API とはちょっと違うから別に説明しようと思って。これ
は次回まで取っておくとして」
『 CString はよくわかんないけど new っていうので確保してるとして?』
「 new で確保したメモリは、 delete っていうので解放する必要がありま
す」
『 GlobalAlloc() の GlobalFree() みたいな感じね』
「で、この解放を str がなくなるときに自動的にする仕組みがあるんで
す」
『自動的に?』
「さっき、デバッグモードでコンストラクタの中に入った後、一度ステップ
アウトして、次にステップインして」
『ほい。あ、なんかの関数に入った』
CString::~CString()
{
// 略。
}
「この、コンストラクタに ~ が付いた、変数がなくなるときに自動的に呼
ばれるメンバ関数を【デストラクタ】って言います」
『お、ここで出てくるんだ。っていうか、ホントにコンストラクタの逆だ
ね』
「うん、コンストラクタとデストラクタも対ってことになるね。で、このデ
ストラクタでは FreeData() を呼んでて」
『そっちは AllocBuffer() の逆でメモリ解放するわけね』
「このメンバ関数の中では delete で解放してるから」
『 new で確保したのを delete で解放、だから大丈夫なわけね』
「そういうこと。まとめると」
1: CString を作る。
2:コンストラクタが呼ばれて自動的にメモリを確保。
3: CSting を使う。
4:関数から抜けて CString がなくなる。
5:デストラクタが呼ばれて自動的にメモリを解放。
「これが自動的だから、確保と解放が対になってて、だから new で確保し
たのを確実に delete で解放できるんです」
『確実に?』
「変な使い方しない限り、確実に」
『変な使い方?』
「文字列を出力するときとかに中のポインタを取得できるわけだから、それ
を free() したりとか」
『げ。ホントに変な使い方だ……』
「まぁそういうのはわざとしない限り大丈夫だとは思うから」
『確保と解放は1対1、になるわけね』
「それに、コンストラクタとデストラクタを使うと他にもメリットがあるん
です」
『メリット?』
「特にデストラクタを使うメリットが多いかな。まず、 malloc() とかの関
数だけの処理だと、解放が面倒なんです。たとえば」
void AllocAndFreeWithCheck()
{
// このデータが入る領域を確保します。
const char *const DATA = "あいうえお";
// まず確保。
char *pch = (char *)malloc( strlen( DATA ) + 1 );
// たとえばここで何かエラーチェックをします。
if( 0 == 0 )
{
// エラーが発生したらここに来て関数から抜ける場合、
// ここでも free() する必要があります。
free( pch );
return;
}
// コピーしてから出力。
strcpy( pch, DATA );
TRACE( "%s\n", pch );
// 解放します。
free( pch );
}
「みたいに、途中で関数を抜ける場合」
『げ、その都度 free() しなきゃいけないんだ』
「クラスなら、関数から抜けるときに自動的にデストラクタが呼ばれるか
ら」
『そういう心配しなくていいってわけね』
「それと、たとえば」
free( pch );
free( pch );
「っていうふうに2度解放しちゃったりするとエラーになります」
『げげ。これもデストラクタを使えば気にしなくていいんだもんね』
「動的に確保したメモリみたいに、扱いが難しいものはクラスに入れると楽
になるから。クラスのことはこれから少しずつ教えていくけど、そういった
メリットを憶えておいて」
『はーい』
/*
Preview Next Story!
*/
『今回、ちょっと難しい……』
「 new とか delete とか、まだちゃんとしてないのを使ってるしね」
『来週教えてくれるんだっけ』
「うん」
『じゃあ new と delete を先に教えてくれればよかったのに』
「ところがそうはいかないんです」
『へ?』
「というわけで次回」
< Version 11.12 new と delete とコンストラクタとデストラクタ >
『につづく!』
「というわけで、コンストラクタとかも関係してくるんです」
『またややこしくなりそう……』