ゆとりーなの日記

日記的な事を書いて行くと思はれる

ムーブってそういうものなのか

const付きのローカル変数を返す関数ではムーブコンストラクタではなくてコピーコンストラクタが働くという噂を聞いたので試してみました。

#include <memory>
#include <iostream>

class Hoge {
 public:
    explicit Hoge(int size) : ptr_(new int[size]), size_(size) {
        std::cout << "コンストラクト" << std::endl;
    }
    Hoge(const Hoge &rhs) : ptr_(new int[rhs.size_]), size_(rhs.size_) {
        std::memcpy(ptr_, rhs.ptr_, size_);
        std::cout << "コピーコンストラクト" << std::endl;
    }
    Hoge(Hoge &&rhs) : ptr_(rhs.ptr_), size_(rhs.size_) {
        rhs.ptr_ = nullptr;
        std::cout << "ムーブコンストラクト" << std::endl;
    }
    ~Hoge() {
        delete[] ptr_;
        std::cout << "デストラクト" << std::endl;
    }
 private:
    int *ptr_;
    int size_;
};

Hoge initHoge1() {
    Hoge hoge(10);
    return hoge;
}

Hoge initHoge2() {
    const Hoge hoge(10);
    return hoge;
}

const Hoge initHoge3() {
    const Hoge hoge(10);
    return hoge;
}

int main() {
    Hoge hoge1(initHoge1());
    Hoge hoge2(initHoge2());
    Hoge hoge3(initHoge3());
    return 0;
}
出力:
コンストラクト
ムーブコンストラクト
デストラクト
コンストラクト
コピーコンストラクト
デストラクト
コンストラクト
コピーコンストラクト
デストラクト
デストラクト
デストラクト
デストラクト

噂は本当でした。戻り値の型にconstが付いている時はムーブコンストラクタが動かないだろうなということは容易に想像できますが、constなし戻り値でも返す値自体にconstが付いているとその変数は壊せないからムーブコンストラクタが働かないというのは意外に盲点な気がします。const教的には厳しい現実です。
でも大丈夫です。最適化をばっちりかければ生成関数のような場合みんなNRVOかかってコンストラクタ呼び出し3回になってしまいます。というわけで個人的にはコンパイラを信用して返す値としてのローカル変数にconst付けてもいいのかなという感じがしてます。
追記:
melpon氏の話が気になったので、試しに上のコードでコピーコンストラクタをprivateにしたところ、次のようなエラーがでました。

error C2248: 'Hoge::Hoge' : private メンバー (クラス 'Hoge' で宣言されている) にアクセスできません。

このエラーがinitHoge2()、initHoge3()のreturnの部分、Hoge hoge3(initHoge3());の部分で出ます。まあムーブコンストラクタが働かないので当然と言えば当然ですが、このエラーはNRVOのよって最終的にコピーコンストラクタ呼び出しが消えるとしても出てしまうエラーであるということに考慮すると、コピー不可だけどムーブ可能なオブジェクトに対しては、生成関数に対して戻り値の型、返す値としてのローカル変数にconstを付けることは出来ないということになりそうです。