ムラサメ研究ブログ

主にゲームやプログラミングのログ

右辺値参照~完全転送まで100%理解するぞ! part4 Universal Reference その前に

こうやって 人に見せようと書く事は自分の理解になる

復習DEATH

前回の復習。右辺値参照を関数の引数にするには 一見コピーに見えるが void hoge( X x ) とプロトタイプすればよい 呼び出し側で hoge( std::move(x) ) とすればムーブコンストラクタが使われる という事 で 復習

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    ~X(){
        cout << "destuct" << endl;
    }
};

void hoge(X x){
    cout << "hoge(X)" << endl;
}

int main()
{
    {
        X x;
        hoge( x );
    }
    cout << "____" << endl;
    {
        const X x;
        hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        hoge( X() );
    }
    cout << "____" << endl;
    {
        hoge( std::move(X()) );
    }
}



constuct
copy
hoge(X)
destuct
destuct
____
constuct
const copy
hoge(X)
destuct
destuct
____
constuct
move
hoge(X)
destuct
destuct
____
constuct
hoge(X)
destuct
____
constuct
move
hoge(X)
destuct
destuct

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

std::move() で呼ぶとムーブされ、何もつけないとコピーされる。正しい使い方だ。 少し気になるのは X() と右辺値で直接呼んだ場合は一時オブジェクトが生成されず効率的だが std::move( X() ) とすると、一時オブジェクトを生成し、それを右辺値にキャストしムーブコンストラクタが発生する (最適化で このへんは最適化されると思うけど、実装依存なので 可能なら コードで最適化しておきたい)

本題

上のコードで納得いかないところ オブジェクトの参照だけで良い時にも無駄にオブジェクトのコピーが走る 納得いかないので 調査をする

前回は一緒に受け取るようにした 参照、const参照、右辺値参照に分割してみる (先にこちらから説明すべきであった 反省)

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    X& operator =(X& x){
        if(&x==this) return(*this);
        cout << "copy assignment" << endl;
        return(*this);
    }
    X& operator =(const X& x){
        if(&x==this) return(*this);
        cout << "const copy asignment" << endl;
        return(*this);
    }
    X&operator =(const X&& x){
        if(&x==this) return(*this);
        cout << "move asignment" << endl;
        return(*this);
    }
    ~X(){
        cout << "destuct" << endl;
    }
    
    void foo(){};
};

static X xx;
/*
void hoge(X x){
    xx = x;
    cout << "hoge(X)" << endl;
}
*/
void hoge(X& x){
    xx = x;
    cout << "hoge(X&)" << endl;
}
void hoge(const X& x){
    xx = x;
    cout << "hoge(const X&)" << endl;
}
void hoge(X&& x){
    xx = std::move(x);
    cout << "hoge(X&&)" << endl;
}


int main()
{
    cout << "____" << endl;
    {
        X x;
        hoge( x );
        x.foo();
    }
    cout << "____" << endl;
    {
        const X x;
        hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        hoge( X() );
    }
    cout << "____" << endl;
    {
        hoge( std::move(X()) );
    }
    cout << "____" << endl;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

面倒なので static変数にコピーしているが、普通はクラスのメンバに代入するだろう (explicit付けたよ! 前回の例では つけられないが ムーブコンストラクタにはexplicit付けるべき)

左辺値参照が引数に入ってきたら、中身を自分にコピー 右辺値参照であればムーブ

バッチリ!!

ところがこれを前回のように hoge(X x) で両方を受け取ると困った事になる

xx = x; と書けば ムーブ出来る時でもコピーになるし xx = std::move(x); では、常にムーブになる

ではやはり、 参照、const参照、右辺値参照 と常に3種類の関数が必要なのか??? また 引数がふえれば 6種類、9種類・・・ 組み合わせ爆発してしまう

そこで登場するのが Universal Reference

次回ご期待!