#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のトップページをご覧ください。