95
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

7分でわかる右辺値参照

Last updated at Posted at 2020-07-08

はじめに

右辺値参照はc+11で規格化された仕様。右辺値参照の説明記事は多くあるが、社内勉強会用には詳しすぎるので、簡易化した資料を作った。これを公開する。

右辺値、左辺値、右辺値参照、左辺値参照、ムーブ、ユニバーサル参照、完全転送という用語を理解すればよい。


左辺値、右辺値とは?

左辺値とは、変数に代入されている値。
右辺値とは、変数に代入する前の定義や計算結果。何もしなければ消えて使えなくなってしまう値
例を見た方が理解が早いはず。

  int x = 1;          //xが左辺値、1は右辺値
  int y = x + 1;      //yが左辺値、x+1は右辺値 xは左辺値
  int z = f(y + 2);    //yが左辺値、f()の関数の戻り値は右辺値、y+2も右辺値

  struct Point
  {
    int x = 0;
    int y = 0;
  };
  Point pt = Point();  //ptはは左辺値、Pointコンストラクタで生成したオブジェクトは右辺値
  
  1;  //1は右辺値
  f(z);  //f()の関数の戻り値は右辺値、戻り値はコピーも束縛もしていないのでこの後使えない

このように代入文の=の左側、右側という意味だけでない。


左辺値参照、右辺値参照とは?

左辺値参照は、左辺値を束縛※すること、またその参照変数。
右辺値参照は、右辺値を束縛※すること、またはその参照変数。

※束縛対象を=で代入、もしくは、束縛対象を関数の実引数にする形で、参照変数に関連付けること。
参照変数はそのまま使うと束縛対象(参照元)の値を返す。また、参照に参照を代入すると代入元の束縛対象を代入先の束縛対象にする。

  //[]は束縛対象(参照元)
  //<>はコピーされた値
  int x=1;                        //xは左辺値<1>            1は右辺値
  int& lref = x;                  //lrefが左辺値参照[x]    xは左辺値
  int& lref2 = lref;              //lref2が左辺値参照[x]  lrefは左辺値参照[x]
//  int& lref3 = 1;               //lref3が左辺値参照     1は右辺値 →Error
  int y = lref;                   //yが左辺値<xの値>      lrefは左辺値参照[x]

  int&& rref = 1;                 //rrefが右辺値参照[1]    1は右辺値
  //int&& rref2 = x;              //rref2が右辺値参照     xは左辺値 →Error
  //int&& rref3 = lref;           //rref3が右辺値参照     lrefは左辺値参照 →Error
  //int&& rref4 = rref;           //rref4が右辺値参照     rrefは右辺値参照 →Error
  int&& rrefm = std::move(lref);  //rrefmが右辺値参照      std::move(lref)は右辺値→別途解説
  int w = rref;                   //wが左辺値<1>          rrefは右辺値参照[1]

補足としてconst左辺値参照も例示。const右辺値参照は普通使われない。

  const int& clref = x;           //clrefがconst左辺値参照[x]  xは左辺値
  const int& clref2 = lref;       //clref2がconst左辺値参照[x] lrefは左辺値参照[x]
  const int& clref3 = 1;          //clref3がconst左辺値参照[1] 1は右辺値 →特別にOK
  int z = clref;                  //zが左辺値<xの値>  clrefはconst左辺値参照[x)

ムーブとは?

c++11の代入とコンストラクタにはコピーとムーブとがある。コピーはデータを全てコピーするので比較的重い処理になるのに対し、ムーブは基本的にポインタとサイズ情報のコピーだけを行うので非常に軽い。ただし、ムーブというだけに、ムーブ元のオブジェクト内のデータはムーブ後不定になるのでそのオブジェクトは使えない。ムーブ先のオブジェクトが、ムーブに対応している場合、左辺値をstd::move()で囲って、代入、もしくは、コンストラクタの引数に設定するだけで、ムーブできる。

  std::string str = "abc";
  std::string str2 = str;//str2をコピー
  std::string str3 = std::move(str);//str3にstrの中身をムーブ、この後strの利用は保証されない
  std::string str4(std::move(str3));

ここは詳細なので、読み飛ばしてもかまいません。std::move()は左辺値を右辺値にキャストする。右辺値なので、代入した場合、コピーオペレータでなくoperartor=(const type&)、ムーブオペレータoperartor=(type&&)が使われる。ムーブオペレータは、ポインタの挿げ替えとムーブ元オブジェクトを無効にするコードが実装されている。const右辺値参照は普通使われないと書いたが、それはconstなオブジェクトを無効化(内部を変更)すべきでなく、使い所がないため。


#ユニバーサル参照(&&で左辺値も束縛できる特別な例外)
auto変数やtemplate変数の&&による参照は、右辺値だけでなく、左辺値も束縛可能。

  template <typename T>
  void g(T&& urefa) 
  {
  }
///----------------------
  int x=1;                        
  int& lref = x;              //lrefが左辺値参照
  const int& clref=x; 
  int&& rref = 1;             //rrefが右辺値参照[1]          1は右辺値
  //int&& rref2 = x;          //rref2が右辺値参照           lrefが左辺値参照 → Error 
  //int&& rref3 = lref;       //rref3が右辺値参照           lrefが左辺値参照 → Error 
  //int&& rref4 = rref;       //rref4が右辺値参照           rrefが右辺値参照(左辺値) → Error 
  //int&& rref5 = clref;      //rref5が右辺値参照           clrefがconst左辺値参照 → Error 
  auto&& uref   = 1;          //urefがユニバーサル参照[1]    1は右辺値 
  auto&& uref2  = x;          //uref2がユニバーサル参照[x]   xは左辺値 
  auto&& uref3 = lref;        //uref3がユニバーサル参照[x]   lrefが左辺値参照[x]
  auto&& uref4 = rref;        //uref4がユニバーサル参照[1]   rrefが右辺値参照[1]
  auto&& uref5 = clref;       //uref5がユニバーサル参照[x]    clrefがconst左辺値参照[x]
  g(1);                        //g()の仮引数はユニバーサル参照  1は右辺値
  g(x);                        //g()の仮引数はユニバーサル参照  xは左辺値
  g(lref);                     //g()の仮引数はユニバーサル参照 lrefは左辺値参照

完全転送とは?

ユニバーサル参照が束縛した値が、右辺値か左辺値かという型情報も保持して転送すること。std::forwardを使う。

void ref(int& a) { std::cout<< "左辺値参照" <<std::endl;}    //左辺値参照版
void ref(int&& b) { std::cout << "右辺値参照" << std::endl; }//右辺値参照版、bは右辺値のみ受け取れる

template <typename T>
void h(T&& urefa2)
{
  ref( rrefa2 ) ;//左辺値参照版がコールされる
}

template <typename T>
void h_with_forward(T&& urefa3)
{
  ref( std::forward<T>(u
refa3) ) ;//右辺値参照版、左辺値参照版、どちらが呼ばれるかは実引数しだい。
}

///----------------------
std::string str = "abc";
int x=1;

h(std::move(str));//std::move(str)は右辺値、出力:左辺値参照
h(1); //1は右辺値、出力:左辺値参照
h(x); //xは左辺値、出力:左辺値参照

h_with_forward(std::move(str));//std::move(str)は右辺値 出力:右辺値参照
h_with_forward(1); //1は右辺値 出力:右辺値参照
h_with_forward(x); //xは左辺値 出力:左辺値参照

さいごに

ビュー数を稼ぎたいわけでないが、なんとなく、キャッチーなタイトルをつけてみた。より詳細が知りたければ、以下のサイトがよいと思う。
https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html


おまけ

A=Bのとき

A\B 左辺値 const左辺値 左辺値参照 const左辺値参照 右辺値 右辺値参照 ユニバーサル参照
左辺値 コピー コピー コピー コピー コピー コピー コピー
const左辺値 コピー コピー コピー コピー コピー コピー コピー
左辺値参照 束縛 NG 束縛 NG NG 束縛 束縛
const左辺値参照 束縛 束縛 束縛 束縛 束縛 束縛 束縛
右辺値参照 NG NG NG NG 束縛 NG NG
ユニバーサル参照 束縛 束縛 束縛 束縛 束縛 束縛 束縛
95
80
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
95
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?