Version 11.12
new と delete とコンストラクタとデストラクタ
「前回はコンストラクタとデストラクタについて見てみました」
『作られるときと削除されるときとでペアになるから便利なんだったよね』
「そのとき一緒に、 CString は new と delete でメモリの確保と解放をす
るって説明しました」
『前にちょっと教えてもらったことあるよね』
「 Version 4.13 ( No.063 ) とか Version 5.22 ( No.087 ) とか
Version 9.08 ( No.169 ) とかだね」
『…… malloc() とかより全然使ってるじゃん』
「それは、 new と delete の方が使いやすいから。前に malloc() を使っ
たときって、文字列を確保したよね」
『うん、 char の配列だよね』
「じゃあ今度は、 malloc() で int を確保してみます」
void CreateInt()
{
// int のサイズ分だけメモリを確保。
int *pi = (int *)malloc( sizeof( int ) );
// 使います。
*pi = 100;
TRACE( "%d\n", *pi );
// 100
// そして解放。
free( pi );
}
『ん? サイズ指定するのに sizeof() 使ってる』
「 int みたいにサイズが1バイトより大きいものは sizeof でサイズを取
得してそれを使います」
『なんで char はいいの?』
「 char は1バイトだからね。ただ、正確には」
int *pi = (int *)malloc
( sizeof( char ) * ( strlen( DATA ) + 1 )
);
「ってする方がいいかな」
『 char を sizeof してる』
「 malloc() の引数に渡す値は〈何バイト確保するか〉。だから malloc()
には【変数のサイズ * 要素の数】を渡すのが基本。 sizeof( char ) は 1
だから省略できるっていうだけの話」
『でも最初の例だと * 要素数ってないよね』
「それは変数が1個だけ、つまり配列として確保しないから。だから、たと
えば int[4] みたいな配列と同じように確保したい場合には」
int *pi = (int *)malloc( sizeof( int ) * 4 );
「ってします」
『つまり、普通の変数なら * 4 とかはいらなくて、配列なら * 4 ってして
要素数指定するってことね』
「そういうこと。言い換えると、メモリ上は、普通の変数もポインタも関係
ないってこと」
『?』
「 malloc() は、確保するときに普通の変数か、配列か、なんて全然気にし
ないで取ってくるから。たとえば、最初の例の」
int *pi = (int *)malloc( sizeof( int ) );
「って確保したポインタを、普通の1個の変数として使うなら」
*pi = 100;
「っていうふうに使うでしょ。でもこれを、配列のように使う事もできるっ
てこと。たとえば」
pi[0] = 100;
『ってゆーか、ポインタだからむしろこっちの方が自然な気がする……』
「そうだね、あまり *pi って使い方しないかも。それでも、普通の変数と
しても使えるんだってことは忘れないで」
『はーい』
「で、話がそれたけど、これと同じ事を new と delete でしてみます」
void CreateIntWithNew()
{
// int を作成。
int *pi = new int;
// 使います。
*pi = 100;
TRACE( "%d\n", *pi );
// 100
// そして解放。
delete pi;
}
『あれ? サイズの指定がない!』
「そう、 new でのメモリの確保は」
int *pi = new int;
「ってします。ここではサイズを指定しません。って言うよりも、実は〈メ
モリを確保する〉って表現自体が間違い」
『どゆこと?』
「 malloc() や API を使った時って、メモリをまず確保して、それを好き
な型のポインタにキャストして使ってたでしょ」
『キャストって』
int *pi = (int *)malloc( sizeof( int ) );
『だと (int *) のところだよね』
「そうそう。これはつまり〈確保したメモリ領域を int として使います
よ〉って宣言しているだけで、実際にそのメモリ領域はどんな型として
使ってもいいんです」
『前にもそんなこと言ってたね』
「 Version 4.11 ( No.061 ) で言ったよね、メモリの中に〈変数の区切り
はない〉って。確保したメモリをどう使おうと自由なんです」
『なるほどねー。で?』
「で、 new は違うってこと。もう一度見てみて」
int *pi = new int;
「こうやって【 new 型】ってすると、その型の変数が作られて、そのアド
レスが返ってきます」
『作られるってどこに?』
「メモリの中」
『……それのどこが malloc() と違うの?』
「まず、キャストしてないでしょ」
『うん、それはわかってる』
「ってことは、基本的にこれは int として使ってってこと。確保した領域
が int のサイズ分ってのは確かなんだけど、それをたとえば char[4] みた
いな使い方はしないでってこと」
『つまり int 専用の領域ですよ、ってこと?』
「そういうこと。つまり、 new は〈指定された変数を作る〉機能があるん
です」
『 malloc() は確保だけして、 new は変数を作る……って言っても、やっ
ぱりメモリを確保して返すだけじゃないの?』
「で、実はもうひとつとっても重要な機能が new と delete にはあるんで
す。たとえば、 CString を作ってみます」
void CreateCStringWithNew()
{
// CString を作成。
CString *pcStr = new CString;
// 使います。
*pcStr = "あいうえお";
TRACE( "%s\n", *pcStr );
// あいうえお
// そして解放。
delete pcStr;
}
『だいたい同じ。ってゆーか普通にできてるよね……なんか変なところある
の?』
「ううん、ないよ。むしろ問題があるのは、次の malloc() を使って確保す
る方」
void CannotCreateCStringWithMalloc()
{
// CString のサイズだけ確保。
CString *pcStr
= (CString *)malloc( sizeof( CString ) );
// 使います。
*pcStr = "あいうえお";
TRACE( "%s\n", *pcStr );
// アクセス違反発生!!
// そして解放。
free( pcStr );
}
『げ、こっちはエラー出た!!』
「なぜそうなるかっていうと、 malloc() はコンストラクタを呼んでくれな
いから」
『コンストラクタって、変数が作られるときに呼ばれるんだよね』
「そう。でも、この例だと、変数が作られてないよね」
『作られてない? だって malloc() ……あ! もしかして malloc() って
メモリ確保してるだけだから、ってこと!?』
「そういうこと。 malloc() は CString が入るサイズだけメモリ領域を確
保して、はいこれを使ってくださいってポインタを返すだけ。ってことは、
そこは CString が入る領域はあるんだけど、実際には」
『 CString が入ってない……』
「そういうことになるかな。で、 new にはコンストラクタを、 delete に
はデストラクタを呼ぶ機能があって、こっちなら」
『ちゃんと CString って変数が作られる……変数が作られる、って意味は
そういうことなのね』
「だから、 Version 11.10 ( No.210 ) で言ったように、結局は new と
delete を使わなくちゃいけないんです」
『でも、 malloc() 使ってるサンプルコードって多いよね』
「もちろん絶対じゃないよ。クラスじゃなければコンストラクタもデストラ
クタもないから」
『 malloc() でも問題ない、と』
「それに、特殊な方法で確保するときには」
『 API のを使わなきゃいけない?』
「そういうこと。それに、実は new と delete は、それ自身ではメモリを
確保してないんです」
『へ?』
「というわけで次回に続く!」
/*
Preview Next Story!
*/
『なるほどね、コンストラクタとデストラクタ』
「それに new と delete 」
『このふたつは密接に結びついてるわけね』
「っていうより、まずコンストラクタとデストラクタを作りたくて」
『でも malloc() だと呼べないから』
「 new と delete を作った」
『ってことなのね』
「というわけで次回」
< Version 11.13 new と delete とそのほか色々 >
『につづく!』
「そういう、機能が生まれた背景も想像すると憶えやすいかも」
『背景?』
「そう、背景」