Version 11.22
CString の注意点
「今回は、 CString を使う上での注意点について説明します」
『注意点? なんかすごく普通に使えそうだけど』
「それが危険。なんとなく使ってるとそれが間違った使い方だって気付かな
いからバグになったりするんです。しかもコンパイルエラーにならないし」
『げ』
「まず確認しておいて欲しいのは、次のプログラムは間違いってこと」
char *GetPointer_Bad()
{
char *pch = "あいうえお";
return pch; // ダメ!
}
『中の変数のポインタを返してる……って、その文字列は関数から出たらな
くなっちゃうからダメ、なんだよね。ついこの前やったでしょ』
「 Version 11.02 でやったね。じゃあ、これはどう?」
CString GetString()
{
CString cStr = "あいうえお";
return cStr;
}
『あ、 CString になった……これ、どうなんだろう。 MFC ってこういう関
数多いから、大丈夫だと思うんだけど、確証ないし』
「だね。結論から言えば、普通に使うなら大丈夫」
void Use_GetString()
{
CString cRecvStr = GetString();
TRACE( "%s\n", cRecvStr );
// あいうえお
}
『あ、ホントだ』
「これができるのは、2重のコピーのおかげ」
『2重のコピー?』
「まず、 GetString() の方で」
return cStr;
「ってしてる部分。このとき、戻り値用に新しく CString が作られて、そ
の中に cStr がコピーされます」
『つまり CString がふたつできるってこと?』
「そういうこと。この戻り値用の CString を仮に _cRetStr とします。さ
て、 GetString() の関数から抜けたとき、中の変数の cStr は削除されま
す」
『やっぱなくなるのね』
「ところが、戻り値の _cRetStr はこの時点では削除されません」
『関数から抜けてるのに?』
「そう。削除されちゃうと返せないからね。で、この戻り値が、関数呼び出
しに置き換わるような感じになります」
CString cRecvStr = _cRetStr;
『おー、これならコピーできるね。そっか、バケツリレーの要領なんだ』
「そゆこと。ちなみにこの次の行に移ると _cRetStr は削除されます」
『この辺って直に見ることってできる?』
「うん。 return の行でブレークポイント置いてステップインしていくと」
『お、コンストラクタとかデストラクタとか呼ばれてる!』
「先に呼ばれてるコンストラクタが戻り値のってこと」
『なるほどねー』
「というわけで、こういう使い方なら問題なし」
『……ってことは、問題のある使い方があるんだ』
「そう、それがこれ」
void Use_GetString_Bad()
{
const char *pchRecv = GetString();
TRACE( "%s\n", pchRecv );
}
『 char のポインタで受け取ってる……』
「ってことは、この部分は」
const char *pchRecv = _cRetStr;
「ってなってるってこと。 CString には operator LPCTSTR があるから」
『中のポインタが pchRecv に入るね』
「でもそれはコピーじゃないでしょ。 _cRetStr の中のポインタを持っただ
け。で、その _cRetStr がなくなったら?」
『そのポインタはもう使えない……』
「というわけ。つまり、 CString を戻り値として返してるのを、 const
char * で受け取っちゃいけないってこと」
『でもこれ、やっちゃいそう……』
「ま、これは CString が悪いってことも言えるんだけど」
『え、そなの?』
「 CString::operator LPCTSTR() がなければ、これってできないでしょ」
『あ……そっか、さっきのところでコンパイルエラーになるね、変換できな
いから』
「ポインタを返すメンバ関数を別に用意する、って方法ならこういう間違い
は起きないけどね。まぁ、そのメンバ関数が GetData() だとして」
const char *pchRecv = _cRetStr.GetData();
『うわ意味なー』
「ま、そういうことを気付かずにしちゃったのが」
『さっきの例って事ね』
「で、ポインタじゃなくて普通に CString で受け取れば大丈夫な理由は、
ちゃんとコピーするから」
CString cRecvStr = _cRetStr;
「この部分で、 _cRetStr の中の文字列を cRecvStr にコピーしてるから大
丈夫」
『ってことは、ふたつできちゃうってことよね。コンストラクタとデストラ
クタが呼ばれまくったりしてたし、なんか無駄って気も』
「無駄な分安全、ってところかな。参照って憶えてる?」
『ポインタみたいなのだよね、 Version 3.22 ( No.047 ) とかでやった
ね』
「たとえばこれは?」
CString &GetStringRef_Bad()
{
CString cStr = "あいうえお";
return cStr; // ダメ!
}
『って、さっきのポインタと同じじゃん! 中の cStr が関数抜けたら消え
ちゃうんだから、その参照返しちゃだめでしょ』
「その通り。だからこれはダメ。ところが、 CString::operator =() のリ
ファレンス見てみて」
『んーっと……げげ!!』
const CString& operator =( const CString& stringSrc );
『なんで参照返してるの?? あ、中で new とかして作ってるとか』
「そしたら外で delete しなきゃいけないけど、参照は delete ってできな
いから」
『う……』
「この = の使い方はこんな感じ」
void Use_CStringRef()
{
CString cStr1 = "あいうえお";
CString cStr2;
CString cStr3;
cStr3 = cStr2 = cStr1;
TRACE( "%s\n", cStr1 );
TRACE( "%s\n", cStr2 );
TRACE( "%s\n", cStr3 );
// あいうえお
// あいうえお
// あいうえお
}
「注目して欲しいのはこの行」
cStr3 = cStr2 = cStr1;
「【演算子の結合規則】って憶えてる?」
『えーっと、確か右が先で左が後とかだよね』
「そう。 Version 6.10 ( No.110 ) でやったね。 = は右が先、左が後だか
ら、まず」
/* cStr3 = */ cStr2 = cStr1;
「の、コメントしてない部分が先に実行されます」
『 cStr2 にも〈あいうえお〉が入るわけね』
「で、さっきの参照の種明かしなんだけど、実はこの参照は cStr2 自身の
参照なんです」
『 cStr2 自身???』
「そう。だから、この結果は」
/* cStr3 = */ cStr2;
「になるわけ」
『あ、そーか、それでこれがそのまま代入されるわけねー』
「そゆこと」
『でもさ、それなら参照使う必要ないんじゃ……あ』
「そう、わざわざ複製とか作るとそれだけ手間が掛かるからね。それに、仕
組み的にも参照の方が合ってるでしょ?」
『確かに、入れられた方がそのまま残ってて、それを今度は入れてあげる、
と』
「そいういうこと。重要なのは、戻り値に参照を使うこと自体がいけないん
じゃなくて」
『関数の中の変数の参照を返しちゃダメ、ってことね』
「そういうこと。この辺の仕組みを知っておくことが重要かな」
『そういえば、自分自身の参照ってどう返すの?』
「 this って憶えてる?」
『げ、あのよくわかんないの!』
「うん、そのよくわかんないの…… this って自分自身だから、それを返せ
ばいいわけ」
『それだけ聞けばなんとなくわかるけど、たぶんよくわかってない』
「かもね。その辺は次の機会、かな」
『次の機会?』
/*
Preview Next Story!
*/
『ねー、次の機会って?』
「かなり先、自分でクラスをどんどん作れるようになったら」
『それって10年後?』
「ちょっと笑えないかも」
『う”』
「それと、文字列編は次回で終わり」
『へ?』
「というわけで次回」
< Version 11.23 文字列編まとめ >
『につづく!』
「文字列編の次はものすごく混乱する話があるから覚悟して」
『げげげ!!』