web-dev-qa-db-ja.com

このコードスニペットでコピーコンストラクターが2回呼び出されるのはなぜですか?

コピーコンストラクターのしくみを理解するために、いくつかのことを試しています。しかし、なぜ_x2_の作成のためにコピーコンストラクターが2回呼び出されるのか理解できません。 createX()の戻り値が_x2_にコピーされるときに一度呼び出されると想定していました。
また、SOに関するいくつかの関連する質問を見ましたが、私が知る限り、ここで尋ねているのと同じ単純なシナリオを見つけることができませんでした。

ちなみに、最適化なしで何が起こっているのかを確認するために、_-fno-elide-constructors_でコンパイルしています。

_#include <iostream>

struct X {
    int i{2};

    X() {
        std::cout << "default constructor called" << std::endl;
    }

    X(const X& other) {
        std::cout << "copy constructor called" << std::endl;
    }
};

X createX() {
    X x;
    std::cout << "created x on the stack" << std::endl;
    return x;
}

int main() {
    X x1;
    std::cout << "created x1" << std::endl;
    std::cout << "x1: " << x1.i << std::endl << std::endl;    

    X x2 = createX();
    std::cout << "created x2" << std::endl;
    std::cout << "x2: " << x2.i << std::endl;    

    return 0;
}
_

これは出力です:

_default constructor called
created x1
x1: 2

default constructor called
created x on the stack
copy constructor called
copy constructor called
created x2
x2: 2
_

私がここで見逃しているか見落としているものを誰かが助けてくれますか?

14
c_student

ここで覚えておかなければならないのは、関数の戻り値は別個のオブジェクトであることです。あなたがするとき

return x;

xを使用して、戻り値オブジェクトをコピーして初期化します。これは、最初に目にするコピーコンストラクターの呼び出しです。その後

X x2 = createX();

返されたオブジェクトを使用して、初期化をコピーしますx2これが、2番目のコピーです。


注意すべきことは、

return x;

可能であれば、xを戻りオブジェクトに移動しようとします。ムーブコンストラクターを作成した場合、これが呼び出されるのを見たことでしょう。これは、ローカルオブジェクトは関数の最後でスコープ外になるため、コンパイラはオブジェクトを右辺値として扱い、有効なオーバーロードが見つからない場合にのみ、左辺値として返すことにフォールバックするためです。

19
NathanOliver

最初のコピーはcreateXの見返りです

X createX() {
    X x;
    std::cout << "created x on the stack" << std::endl;
    return x; // First copy
}

2つ目は、createXによる一時的な戻りからx2を作成することです。

X x2 = createX(); // Second copy

C++ 17では、2番目のコピーが強制的に省略されることに注意してください。

12
Jarod42