右辺値参照~完全転送まで100%理解するぞ! part6 Universal Referenceと std::forward
やっと UniversalReferenceですよ!
その前に復習
右辺値参照を引数にする時は あまり気にせず下記のようにするのが正しいのでしたね
#include <iostream> using namespace std; class X{ public: // explicit付けられない! X(X& x){ cout << "copy" << endl; } X(X&& x){ cout << "move" << endl; } }; void hoge(X x){ cout << "hoge(X)" << endl; } int main() { X x; hoge(x); hoge(move(x)); }
呼ぶときに std::move() しなければコピーコンストラクタ、つければムーブコンストラクタが働く これは 暗黙的変換を利用し XとX&& を受け入れています
でも、コピーせず左辺値参照を渡したい事多いよね? コピーするだけ無駄だし それに STLのなかには explicit付きのものしか呼べない関数もあったりで 明示的に行いたい
そんな時は愚直に 左辺値参照と右辺値参照の関数を2つ(const参照いれて3個)実装すればよい
#include <iostream> using namespace std; class X{ public: // explicit付けられる。付けれるなら絶対付けた方がよい explicit X(X& x){ cout << "copy" << endl; } explicit X(const X& x){ cout << "const copy" << endl; } explicit X(X&& x){ cout << "move" << endl; } }; void hoge(X& x){ cout << "hoge(X&)" << endl; } void hoge(const X& x){ cout << "hoge(const X&)" << endl; } void hoge(X&& x){ cout << "hoge(X&&)" << endl; } int main() { const X x; hoge( x ); hoge( std::move(x) ); }
このようにすれば、当然 右辺値の時と左辺値の時で処理をわけられる
しかし、毎回同じような関数が3個。引数が増えればさらに倍増していく
それを防ぐのが Universal Referenceである
Universal Reference
#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(){}; }; class Hoge{ public: template<class T> void hoge(T&& x){ // x_ = x if lvalue // x_ = std::move(x) if rvale cout << "hoge(T&&)" << endl; } private: X x_; }; int main() { Hoge h; cout << "____" << endl; { X x; h.hoge( x ); } cout << "____" << endl; { const X x; h.hoge( x ); } cout << "____" << endl; { X x; h.hoge( std::move(x) ); } cout << "____" << endl; { h.hoge( X() ); } cout << "____" << endl; { h.hoge( std::move(X()) ); } cout << "____" << endl; } constuct ____ constuct hoge(T&%) destuct ____ constuct hoge(T&%) destuct ____ constuct hoge(T&%) destuct ____ constuct hoge(T&%) destuct ____ constuct hoge(T&%) destuct ____ destuct
template
そして 関数テンプレート void hoge(T&& x) に Tが X&の時とX&&の時を考える
ここは 前回の 参照の圧縮を使えば TがX&の時は hoge(X& x)
TがX&&の時は hoge(X&& x) となる
ところが問題はコメント部分である 引数 xが 左辺値(X&) の時は copy assignment(X& operator =(X&)) を行いたいが 右辺値(X&&)の時は move assignment(X& operator =(X&&)) を行いたい
そんな時に使えるのが std::forward
実際に使ってみる
#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(){}; }; class Hoge{ public: template<class T> void hoge(T&& x){ x_ = std::forward<T>(x); cout << "hoge(T&&)" << endl; } private: X x_; }; int main() { Hoge h; cout << "____" << endl; { X x; h.hoge( x ); } cout << "____" << endl; { const X x; h.hoge( x ); } cout << "____" << endl; { X x; h.hoge( std::move(x) ); } cout << "____" << endl; { h.hoge( X() ); } cout << "____" << endl; { h.hoge( std::move(X()) ); } cout << "____" << endl; } constuct ____ constuct copy assignment hoge(T&&) destuct ____ constuct const copy asignment hoge(T&&) destuct ____ constuct move asignment hoge(T&&) destuct ____ constuct move asignment hoge(T&&) destuct ____ constuct move asignment hoge(T&&) destuct ____ destuct
std::forwardを使えば、T&、const T&、T&& と3つの引数のオーバーロードを1関数で処理できる
とてもすばらしい。