右辺値参照~完全転送まで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(); }
上記コードは 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
復習になるけど、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
上記が正しい関数プロトタイプである
実際には 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) 関数を呼ぶときに 右辺値であればムーブ、左辺値であればコピーされる コピーしたくない時は、呼ぶときに ムーブセマンティクス忘れないように!