#pragma twice

KAB-studio > プログラミング > #pragma twice > 222 Version 11.22 CString の注意点

#pragma twice 222 Version 11.22 CString の注意点

前のページへ 表紙・目次へ 次のページへ

 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 文字列編まとめ >
『につづく!』
「文字列編の次はものすごく混乱する話があるから覚悟して」
『げげげ!!』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。