CStringを関数で戻す場合

解決


瑠璃  2008-09-23 04:26:39  No: 69064

CStringを戻り値にした場合の質問です。

fooSub()が呼ばれた時点で strSub は作られて
returnするときに strSub は開放されてしまうと思いますが
ちゃんと呼び出し側の foo() の str に代入されるのでしょうか?
下記のような感じの場合です。

ちなみに、心配しているポイントは、strSub の文字列の領域等は
ヒープに動的に確保されるのではないかと思いますが、return で
strSub はスコープから抜けるので、strSub のヒープ上の文字列の
領域等は開放されてしまうのではないか?と思ったからです。

CString fooSub() {
    CString strSub;

    strSub = .....; //処理

    return strSub;
}

void foo() {
   CString str;

   str = fooSub();

   .....

   return;
}


isshi  2008-09-23 05:49:41  No: 69065

>ちゃんと呼び出し側の foo() の str に代入されるのでしょうか?
strSubのインスタンス自体は、fooSub()終了時に破棄されますが、
strSubのコピーがfooSub()の戻り値となり、それがstrに代入されるので大丈夫です。


瑠璃  2008-09-23 06:14:54  No: 69066

isshiさん、ありがとうございます。

ただ、非常にシビアな部分が知りたいなって思ってるのですが
この使い方で、大丈夫でしょうか。

つまり
1.return strSubでスタックにCStringのサイズ分の
    戻り値がセットされる。
    CStringのサイズ分なので文字列領域はスタック上には無いと思う。
2.fooSub()が終了して strSub が開放され
    strSub が使っていたヒープ上の文字列の領域開放される。
3.foo()上で1.の戻り値が str にコピーされる。
の順番で処理されるのではと思ったのです。
そうすると、2.で開放されたヒープ上の領域が3.で使用され
ちゃうんじゃないかと。

大丈夫でしょうか?

> strSubのコピーがfooSub()の戻り値となり、それがstrに代入されるので大丈夫です。
とのことですが、strSubのコピー処理(コンストラクタ)がどの時点で処理されているのでしょうか?

関数の呼び出し処理と、戻りがの処理が理解できてなくて申し訳ありませんが宜しくお願いします。


isshi  2008-09-23 06:51:30  No: 69067

1. return strSub; で、strSub のコピーが一時オブジェクトとして生成される。
2. strSub が破棄される。
3. 1.で作成された一時オブジェクトが str にコピーされる。
4. 1.で作成された一時オブジェクトが破棄される。

ただし、一時オブジェクトの生成にはコストがかかるため、
一時オブジェクトの生成が省略されることが認められています。
(戻り値の最適化(RVO))
その場合、
1. return strSub; で str に strSub がコピーされる。
2. strSub が破棄される。
となります。


瑠璃  2008-09-24 02:48:14  No: 69068

たびたび、isshiさん、ありがとうございます。

> 1. return strSub; で、strSub のコピーが一時オブジェクトとして生成される。
ってことが理解できていませんでした。ありがとうございました。

ちなみに、RVOで一時オブジェクトの生成が省略されちゃったときは
> 1. return strSub; で str に strSub がコピーされる。
> 2. strSub が破棄される。
ってことなので、つまり
strのコピーメソッドの方が、strSubのデストラクタより、早く処理されるってことなんですね。
RVOについては調べてみたんですが、正確な動作が理解できていません。
ここらへん(RVO)について、追加コメントあればお願いします。


isshi  2008-09-24 08:41:54  No: 69069

>ってことなので、つまり
>strのコピーメソッドの方が、strSubのデストラクタより、早く処理されるってことなんですね。
はい、そうです。

下記ソースを試してみてください。

VC++2008 Express で試したところ、
debug版ではRVOが実施されず、release版では実施されているようです。

#include <iostream>

class A{
public:
  A()
  {
    std::cout << "コンストラクタ" << std::endl;
  }

  A(const A &)
  {
    std::cout << "コピーコンストラクタ" << std::endl;
  }

  ~A(){
    std::cout << "デストラクタ" << std::endl;
  }

  A & operator=(const A &a){
    std::cout << "代入" << std::endl;
    return *this;
  }
};

A CreateA()
{
  A sub_a;
  return sub_a;
}

int main()
{
  {
    A a;
    a = CreateA();
  }

  std::cin.sync();
  std::cin.get();

  return 0;
}


瑠璃  2008-09-25 09:00:11  No: 69070

isshiさん、コードにて検証までして頂きありがとうございます。

そうですよね、実際に何らかのクラスで実験してみれば良いのですよね。
私も、やってみます。
また、RVOの状況は、コンパイルオプション/Oxの設定でも変わりそうですね。

私にとって VC++ 奥が深いです・・・


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加