Visual C++ 2015 でオブジェクトを値返しした時の挙動について

オブジェクトを値返ししたときの挙動について気になることを調べました。 特に、コンストラクタの呼び出し回数やコピーコンストラクタとムーブコンストラクタのどちらが呼ばれるのかについて調べました。

環境

試したこと

ソースコード

#include <iostream>

class Hoge {
public:
    Hoge() {
        std::cout << "constructor" << std::endl;
    }
    Hoge(const Hoge&) {
        std::cout << "copy constructor" << std::endl;
    }
    Hoge(Hoge&&) {
        std::cout << "move constructor" << std::endl;
    }

    ~Hoge() {
        std::cout << "destructor" << std::endl;
    }
};

Hoge returnHoge1() {
    return Hoge();
}

Hoge returnHoge2() {
    auto hoge = Hoge();
    return hoge;
}

int main() {
    std::cout << "start returnHoge1" << std::endl;
    {
        auto obj = returnHoge1();
    }
    std::cout << "-----------------" << std::endl;
    std::cout << "start returnHoge2" << std::endl;
    {
        auto obj = returnHoge2();
    }
    return 0;
}

実行結果

x86 ビルドでも、x64 ビルドでも同じ結果でした。

Release ビルド(最適化は「実行速度の最大化 (/O2)」)

start returnHoge1
constructor
destructor
-----------------
start returnHoge2
constructor
destructor

Debug ビルド(最適化は「無効 (/Od)」)

start returnHoge1
constructor
destructor
-----------------
start returnHoge2
constructor
move constructor
destructor
destructor

調べた結果

まず、Release ビルドについてです。
returnHoge1 関数とreturnHoge2 関数のどちらの場合も、コンストラクタとデストラクタが1度ずつ呼ばれました。 コピーコンストラクタもムーブコンストラクタも呼ばれません。 これはRVO(Return Value Optimization) という最適化が行われるためです。
続いてDebug ビルドについてです。
returnHoge1 関数のような書き方をした場合はRelease ビルドと同じ結果でした。
returnHoge2 関数のような書き方をした場合はRVO が行われず2つのオブジェクトが生成されました。 そして、コピーコンストラクタとムーブコンストラクタの両方を用意した時はムーブコンストラクタが呼ばれます。
ちなみに上記のコードでムーブコンストラクタをコメントアウトして実行すれば、コピーコンストラクタが呼ばれます。

感想

私は、Debug ビルドでreturnHoge1 関数のような書き方をした場合は2つのオブジェクトが生成されるだろうと思っていたので、 そこは私にとっては意外な結果でした。

参考

今回の調査方法とRVO について、以下の記事を参考にしました。