ここから本文です

c++ オブジェクトを戻り値とする関数 #include <iostream> using namespace st...

fek********さん

2019/5/2315:03:15

c++ オブジェクトを戻り値とする関数

#include <iostream>
using namespace std ;
class sample{
public:

sample( ){cout<<"コンストラクタ\n";} ;
~sample( ){cout<<"デストラクタ\n";} ;
} ;
sample get(){
sample s ;
return s ;
}

について、
int main(){
sample a ;
a=get() ;
return 0 ;
}
の出力は
コンストラクタ
コンストラクタ
デストラクタ
デストラクタ
になるのに対して、
int main(){
sample a=get() ;
return 0 ;
}
の出力は
コンストラクタ
デストラクタ
になります。
なぜでしょうか?

補足//改良版
#include <iostream>
using namespace std ;
class sample{
ㅤsample(){cout<<"コンストラクタです\n";}
ㅤsample(const sample& a){cout << "コピーコンストラクタです\n";}
ㅤ~sample(){cout << "デストラクタです\n";}
ㅤsample& operator=(const sample &a){
ㅤㅤcout<<"代入です\n";
ㅤㅤreturn *this;
ㅤ}
} ;
sample func(){
ㅤsample f ;
ㅤreturn f ;
}
void first(){
ㅤsample a ;
ㅤa=func() ;
}
void second(){
ㅤsample a=func() ;
}

int main(){
ㅤcout << "first\n" ;
ㅤfirst() ;
ㅤcout << "second\n" ;
ㅤsecond() ;
ㅤreturn 0 ;
}
/*(実行結果)
first
コンストラクタです
コンストラクタです
代入です
デストラクタです
デストラクタです
second
コンストラクタです
デストラクタです
*/


改良してみました。
この時
①firstで呼ばれるfuncで宣言したオブジェクトfは("デストラクタです"よりも"代入です"の出力が早かったことから)スコープを外れてもデストラクタが呼ばれていないように思えるのですが何が起きているのでしょうか?

②実行結果より、secondでは代入もコピーコンストラクタも行われていないように思えますが何が起きているのでしょうか?(RVOが関係?)

よろしくお願いします。

閲覧数:
86
回答数:
3

違反報告

ベストアンサーに選ばれた回答

leh********さん

2019/5/2320:24:18

> なぜでしょうか?
原因はコンパイラによる省略です。
コンストラクタにはあなたが記載しているデフォルトコンストラクタ以外にも
コピーコンストラクタ
ムーブコンストラクタ(C++11 以降)
があります。
#これを記載していないため起動してもわからないコードになっています。
これらは、省略が可能な箇所では省略が許されています。
そのためコンパイラによって動作が異なります。
参考までに C++ のバージョンと省略しない場合にどうなるか示します。

デフォルトコンストラクターを def-ctor
コピーコンストラクタを copy-ctor
ムーブコンストラクタを move-ctor
代入 operator を = operator
ムーブ operator を move operator
として
C++14 までは copy / move constructor はコンパイラにより省略が許されています。
C++17 では copy / move constructor の省略が必須になっています。

■C++03 まで copy constructor 省略無しの場合
sample a; // 1: def-ctor
a = get(); // 1: def-ctor, 1: copy-ctor, 1: = operator
sample a = get(); // 1: def-ctor, 2: copy-ctor
sample a = sample(); // 1: def-ctor, 1: copy-ctor

■C++11 以降 move constructor 省略無しの場合
sample a; // 1: def-ctor
a = get(); // 1: def-ctor, 1: move-ctor, 1: move operator
sample a = get(); // 1: def-ctor, 2: move-ctor
sample a = sample(); // 1: def-ctor, 1: move-ctor

  • 質問者

    fek********さん

    2019/5/2411:52:16

    回答ありがとうございます。

    ■C++03 まで copy constructor 省略無しの場合
    sample a; // 1: def-ctor
    a = get(); // 1: def-ctor, 1: copy-ctor, 1: = operator
    sample a = get(); // 1: def-ctor, 2: copy-ctor
    sample a = sample(); // 1: def-ctor, 1: copy-ctor


    a = get(); // 1: def-ctor, 1: copy-ctor, 1: = operator
    では、なぜコピーコンストラクタが呼ばれているのかが理解できませんでした...

    sample a = get(); // 1: def-ctor, 2: copy-ctor
    についても、コピーコンストラクタが1回ではなく2回呼ばれている理由も教えていただけると嬉しいです。

  • その他の返信(2件)を表示

返信を取り消しますが
よろしいですか?

  • 取り消す
  • キャンセル

この回答は投票によってベストアンサーに選ばれました!

ベストアンサー以外の回答

1〜2件/2件中

並び替え:回答日時の
新しい順
|古い順

プロフィール画像

カテゴリマスター

ikt********さん

2019/5/2321:43:03

C++ には、C++03 の時代から
戻り値最適化
Return Value Optimization (RVO)
と呼ばれる最適化があります。
# 言語規格で定義されてるもの。

後者はRVOが効いてるからだろうと思う。

https://blog.kmc.gr.jp/entry/2014/12/20/231430


P.S.
コンパイルエラーにはならないけど、メンバ関数の
の記述、ちょっとヘンです。
関数ボディ終了の閉じブレースの後の ;(セミコロン)
は無用です。

返信を取り消しますが
よろしいですか?

  • 取り消す
  • キャンセル

プロフィール画像

カテゴリマスター

n2q********さん

編集あり2019/5/2512:01:32

〔上の例〕

main 内の sample a ; で1回目の「コンストラクタ」。
get 内の sample s ; で2回目の「コンストラクタ」。

sample 型インスタンスを2回生成しています。


〔下の例〕

get 内の sample s ; で唯一の「コンストラクタ」。

他に sample 型インスタンスを生成している箇所がありません。



~補足に関して~

【①について】

古いコンパイラを使うと理解しやすいかもしれません。

<例:Visual C++ .NET 2003 まで>


func の return f; でコピーコンストラクタが呼び出されます。そして、func の終わりということでデストラクタが呼び出されます。

first に戻り、代入演算子が呼び出されます。右辺のオブジェクトが即座に不要となり、デストラクタが呼び出されます。


<Visual C++ 2005 から>


func の return f; でコピーコンストラクタは呼び出されません。f そのものを返すようなイメージとなります。当然、デストラクタは呼び出されません。Visual C++ 2010 から実現された C++ 0x における「移動」の概念と共通するところがありますね、これは。

first に戻り、代入演算子が呼び出されます。そして右辺の右辺のオブジェクトが即座に不要となり、デストラクタが呼び出されます。


『スコープを外れてもデストラクタが呼ばれていないように思えるのですが何が起きているのでしょうか?』

Visual C++ .NET 2003 までの場合、デストラクタが呼び出されます。つまり、コピーした上で元データを削除。コピーしたものを返す。こういう無駄な動きになっています。

Visual C++ 2005 からはコンパイラの場合はコピーしたものを返すのではなくて、そのものを直接返しているイメージです。このため、func 内でデストラクタは呼び出されないというわけ。



【②について】

『代入もコピーコンストラクタも行われていないように思えますが何が起きているのでしょうか?』

まず、代入は除外します。(代入するようには書かれていないからです。それは初期化、initialization であり、代入 assignment ではないので。)

コピーコンストラクタに関してですが、Visual C++ .NET 2003 までは上述の通り、return 文のところで既に呼び出されています。

そして、return 文から戻った後に、更にコピーコンストラクタが呼び出されてもおかしくないところですが、さすがにそれは行われてなく、その戻り値をそのまま変数 a の初期化済みの姿として受け取っているイメージになります。この部分だけを捉えると C++0x の「移動」にも見えます。

Visual C++ 6.0 まで遡って確認しましたが、これは同じでした。やはり更なるコピーコンストラクタの呼び出しは抑止されています。もっと前のバージョンでどうなるかは未確認です。


Visual C++ 2005 からは、return 文によるコピーコンストラクタの発動は無く、そのままの形でオブジェクトが返ってきていますので、あとは以前のバージョンと同様に、これをそのまま初期化済みのイメージとして取り込む形です。

返信を取り消しますが
よろしいですか?

  • 取り消す
  • キャンセル

みんなで作る知恵袋 悩みや疑問、なんでも気軽にきいちゃおう!

Q&Aをキーワードで検索:

Yahoo! JAPANは、回答に記載された内容の信ぴょう性、正確性を保証しておりません。
お客様自身の責任と判断で、ご利用ください。
本文はここまでです このページの先頭へ

「追加する」ボタンを押してください。

閉じる

※知恵コレクションに追加された質問は選択されたID/ニックネームのMy知恵袋で確認できます。

不適切な投稿でないことを報告しました。

閉じる