たけです。
typedef struct {
CString str1;
CString str2;
} TEST;
TEST hoge() {
TEST test;
test.str1 = hoge1;
test.str2 = hoge2;
return test;
}
呼び出し側
TEST test = hoge();
上記のように、CStringを持つ構造体をリターンした場合って
OKなのでしょうか。いちおう動いてはいるのですが
hoge()関数内で有効なTESTインスタンスがそのままコピーされてきているようで、
その内、落ちそうな気がします。
このような場合、コピーコンストラクタが必要でしょうか?
> hoge()関数内で有効なTESTインスタンスがそのままコピーされてきているようで、
ちゃんとコピーされてるんならええと思うけど。
> その内、落ちそうな気がします。
なぜ?
C++における構造体は,クラスにほぼ同義です。
#デフォルトがprivateでなくpublicなだけ。
なので,TEST構造体には自動で
TEST::TEST (const TEST & obj) : str1(obj.str1), str2(obj.str2) {}
というコピーコンストラクタが生成されています。
ちなみに,
typedef struct { } TEST;
は無名の構造体にTESTという別名をつけているので,コピーコンストラクタは書けません
よ。
struct Test { };
であればTestという構造体を定義しているので,コピーコンストラクタを書けますが。
>なぜ
hoge()関数内のときと、呼んだ後で、str1.m_pchDataを
確認しました。
m_pchDataは、文字列を格納している先のアドレスだから
コピーコンストラクタが呼ばれた後では、CStringの値を再確保するはずだから
hoge()関数内と、呼んだ後では違う値になっているはずだと考えました。
しかし、結果は同じ値を持っていたので、再確保されず
アドレスがそのままコピーされたと考えました。
だから、すでにスコープからはずれたインスタンスの持っていた
アドレスを使っている、戻り値のTESTインスタンスは、そのうちおかしな事になるのでは?と
思いました。
でも、やっぱりどこか勘違いしていますか?
>ちなみに,
>typedef struct { } TEST;
>は無名の構造体にTESTという別名をつけているので,コピーコンストラクタは書けません
>よ。
>struct Test { };
>であればTestという構造体を定義しているので,コピーコンストラクタを書けますが。
これについてはなるほど!
> m_pchDataは、文字列を格納している先のアドレスだから
> コピーコンストラクタが呼ばれた後では、CStringの値を再確保するはずだから
> hoge()関数内と、呼んだ後では違う値になっているはずだと考えました。
これが理由でコケるとすると、
CString a = hello;
CString b = a;
b.SetAt(0,'H');
なんてことすると a = Hello となるはずですよね。
でも大丈夫。ちゃんとコピーされてます。
#include <afx.h>
#include <iostream>
int main() {
CString a = hello;
CString b = a;
b.SetAt(0,'H');
std::cout << a << '\t' << b << std::endl;
return 0;
}
>m_pchDataは、文字列を格納している先のアドレスだから
>コピーコンストラクタが呼ばれた後では、CStringの値を再確保するはずだから
>hoge()関数内と、呼んだ後では違う値になっているはずだと考えました。
それは「外れ」です。
CStringには、「変更時コピー」というテクニックが使われています。
単純にコピーした場合にはポインタだけがコピーされます。
実際に違う値に変更が生じた場合に初めて別領域に独立したメモリが確保されるので
す。
で、同じ文字列領域を指している CString がいくつ存在しているか、
ってのが参照カウントで管理されます。
同じ文字列領域を指している CString すべてが開放され、参照カウントがゼロに
なった時点で初めて領域開放されます。
関数スコープ内の CString は戻り値の一時オブジェクトに参照されるので、
参照カウントがひとつカウントアップします。
関数から抜けるとスコープ内の CString が開放されるので参照カウントが
ひとつカウントダウンされますがゼロにはなりません。
なので、文字列領域は開放されません。
>CString a = hello;
> CString b = a;
> b.SetAt(0,'H');
これ、試してみました。
この場合、a.m_pchDataとb.m_pchDataは
同じ値をとるけど、SetAt()呼び出し時に、領域が
再確保されるようですね。
CStringのソースを追ってみると
なんと、参照されている数を保持しているんですね。
参照されている数が1より大きければ再確保してます。
なるほど。
このような構造をみると、だんだんOKな気がしてきました。
あと、一言助言が欲しいのですが
hoge()内で、TESTインスタンスのstr1.m_pchDataは、
もう、TESTインスタンスはスコープから外れているにも
かかわらず、m_pchDataの領域が、他の処理で使われない理由が
わからないです。
ちょっと、CStringのソースを追い切れませんでした。
>hoge()内で、TESTインスタンスのstr1.m_pchDataは、
>もう、TESTインスタンスはスコープから外れているにも
>かかわらず、m_pchDataの領域が、他の処理で使われない理由が
>わからないです。
何を尋ねているのか僕にはよくわからんです。すんません。m(__)m
> この場合、a.m_pchDataとb.m_pchDataは
> 同じ値をとるけど、SetAt()呼び出し時に、領域が
> 再確保されるようですね。
そ。それが copy-on-write のカラクリ。
> hoge()内で、TESTインスタンスのstr1.m_pchDataは、
> もう、TESTインスタンスはスコープから外れているにも
> かかわらず、m_pchDataの領域が、他の処理で使われない理由が
> わからないです。
return-valueとして関数の外に出て行き、誰かがそれを捕まえるから。
>そ。それが copy-on-write のカラクリ。
この方式知らなかったです。勉強になりました。
>return-valueとして関数の外に出て行き、誰かがそれを捕まえるから。
↑で一応なっとくできました。
解決とします。
でも、どういうやり方で、どこのソースでそれを実行しているのかちょっと気になります。
Tabさんの16:56:59の書き込みを見逃してました。
この書き込みに詳しく書いてありましたね。
納得です。
みなさま、素朴な疑問に答えていただいてありがとうございました。