C++幼女先輩

プログラミング成分多め

右辺値参照~完全転送まで100%理解するぞ! part3 関数呼び出し

いまいちまだ 納得していない部分もある(理解不足

実践的な関数呼び出し

右辺値参照はムーブセマンティクスのために使われる ムーブセマンティクスは主に ・ 所有権の移動 ・ コピーのコストを削減(ポインタのすげ替え)

で使われる

所有権の移動

コピーされたら困る(インスタンスが複数になってはダメ)ものに 例えば スレッドや、ユニークポインタ等で使う

#include <iostream>
#include <thread>

using namespace std;

int main()
{
  thread thread_;
  
  
  // ムーブコンストラクタが呼ばれるので threadは2個出来ない
  thread_ = thread( [] { cout << "done" << endl; });  
  
  // コピーコンストラクタ不可能
  // thread thread2_ = thread_;   

  if (thread_.joinable()) thread_.join();

}

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

上記コードは std::threadは コピーコンストラクタがdeleteされ、ムーブコンストラクタしか無い thread = thread( [] { cout << “done” << endl; });
は、右辺値参照であるため、ムーブコンストラクタが呼ばれ、結果的に threadの所有権が thread
に渡るので 1個しかスレッドが作成されない もし仮に コピーコンストラクタで動いた場合は 一時オブジェクトとしてスレッドが作成され、さらに同じものを thread_にコピーし スレッドは2個作成されるものと思われる

thread thread2 = thread;
は、もちろんコピーコンストラクタは無いため コンパイルエラーになる

同じく unique_ptrもコピーコンストラクタが無く、ムーブコンストラクタのみ許可されているため ポインタが2個になる事はない

コピーのコストを削減

こちらが、よく使う方。

#include <iostream>

using namespace std;

class X{
public:
    X(X& x){
        cout << "copy" << endl;
    }
    X(X&& x){
        cout << "move" << endl;
    }
};


int main()
{
    X x;
    X x1 = x;          // lvalue
    X x2 = move(x);    // rvalue
    X x3 = move(X());  // rvalue
}



copy
move
move

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

復習になるけど、lvalueとrvalueにより コピーとムーブになる

このことを踏まえ 正しい関数呼び出しは

#include <iostream>

using namespace std;

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

X hoge(X x){
    return( move(x));
}

int main()
{
    X x;
    hoge(move(x));
}


constuct
move
move
destuct
destuct
destuct

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

上記が正しい関数プロトタイプである

実際には hogeのreturnには std::moveを書かなくとも、moveしてくれるので書かないのが普通である

ただし、見てわかるとおり hogeを呼ぶ際に std::moveを忘れて hoge(x) とすると

constuct copy move destuct destuct destuct

と、当然のように コピーコンストラクタが走るので、使うときによく確認しなければならない

もちろん、コピー禁止にするなら hogeの引数を X&& にすればよい

#include <iostream>

using namespace std;

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

X hoge(X&& x){
    return( move(x));
}

int main()
{
    X x;
    hoge(move(x));
}


constuct
move
destuct
destuct

と、さらに処理が速くなり、左辺値の場合はコンパイルエラーになるので、ムーブオンリーにできる

まとめ

大体の場合は、関数プロトタイプでは 実体を渡せばいい X hoge(X x) 関数を呼ぶときに 右辺値であればムーブ、左辺値であればコピーされる コピーしたくない時は、呼ぶときに ムーブセマンティクス忘れないように!