C++幼女先輩

プログラミング成分多め

右辺値参照~完全転送まで100%理解するぞ! part1 右辺値参照とは

TL;DR

はじめに、私が右辺値参照を調査したのは、何番煎じかわからない 解説をしたいわけではなく VisualStudioでの動作の違い、universal referenceの特殊化したときの動き、複数の引数をuniversal referenceしたとき デフォルト引数・・・ など、応用したときに色々と悩んだから それを自分の中でまとめようとおもう

とはいっても説明

右辺値参照は、実はそんなにも難しくないと思う 難しいと思う人は、名前に騙されて勘違いされているだけで たった3つの事柄を知れば 理解が一気に深まるとおもう 自分はそうだった。

とりあえず 最初にその一言をいうと

右辺値は名前が悪い。 一時オブジェクト あるいはアドレスを持たないものと考えると良い

std::moveは名前が悪い。移動ではなく右辺値へのキャスト演算子である

関数の仮引数で右辺値参照(hoge&&) で受けても左辺値(hoge&)であるので右辺値にするには std::move()でキャストする

私の場合はこの3つで一気に80%まで理解できた

何番煎じかわからないが 右辺値参照について調査する

右辺値とは

一時オブジェクトだが 細かいのは他の人のブログみてもらいたい

std::string s("hoge");   // &sとアドレスが取れる=実態があるので左辺値
foo( s );               // 左辺値を関数に渡している

str::string("hoge");    // アドレス取れない一時オブジェクト
foo( str::string("hoge") ) // 右辺値を関数に渡している

右辺値の考えが重要になったのは、例えば上記の場合、右辺値を関数に渡すときにオブジェクトのコピーが走る 一時オブジェクトは コピーせず破壊しても良いので無駄である ので、渡すときにコピーを行わず所有権を渡す(ポインタをすげかえる)事が出来れば パフォーマンスがあがる

同様に左辺値も、所有権を渡したいシーンもあるだろう。そんな時は std::move()を使い 右辺値にキャストし所有権を移動出来るようにする

ムーブコンストラクタ

class Hoge{
public:
  Hoge(Hoge &obj){} // コピーコンストラクタ
  Hoge(Hoge &&obj){} // ムーブコンストラクタ
};

書式は上のように hoge&& と、&を2個重ねる

実際に実装しようとするとなかなかに面倒くさい。 移動元のdestructorも呼ばれるためデータが消されないように注意する 自分のムーブを禁止しなければならない 移動代入演算子 operator=(Hoge&&)も実装してね noexpectに気をつけろ ・・・

Hoge(Hoge&&) = default; と、デフォルトのムーブコンストラクタも作成可能だが 条件次第では作れない事もあるので自分で実装する方法を知らねばならない

Microsoftにとても良いページがあったので これを見て設定してほしい https://msdn.microsoft.com/ja-jp/library/dd293665.aspx

std::move()

最初に std::move()は 右辺値へのキャストのみ(移動はしない)と書いた 右辺値参照にキャストするだけだと それが上記のムーブコンストラクタと関係する

#include<iostream>

using namespace std;

void foo( string& s) {
  cout << "lvalue" << endl;
};
void foo( string&& s) {
  cout << "rvalue" << endl;
};


auto main() -> int{
  string s("s");

  foo(string("hoge"));
  foo(s);    
  foo(std::move(s));
}



rvalue
lvalue
rvalue

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

sは左辺値なので、string& の方が呼ばれる。コンストラクターでいうと コピーコンストラクタである が、std::move()をつかうと string&&になり 右辺値参照となっている コンストラクターでいうと ムーブコンストラクタになる

仮引数では左辺値参照である

ここは解せないと最初に感じるポイントだろう string&& s(“hoge”); のようなコードを考えてみる sには右辺値参照の値が入っている 一見 sは右辺値参照であると思うが、思い出してほしい 右辺値というのは アドレスがとれない実体のない一時オブジェクトということだ しかしこの sには 実体がある ので、 右辺値参照をもった左辺値である ややこしい。 ので 関数のプロトタイプ void hoge(int &&i) のiは 左辺値なのである

#include<iostream>

using namespace std;

void foo2( string& s) {
  cout << "lvalue" << endl;
};
void foo2( string&& s) {
  cout << "rvalue" << endl;
};

void foo( string& s) {
  foo2(s);
};
void foo( string&& s) {
  foo2(s);
};

auto main() -> int{
  string s("s");

  foo(string("hoge"));
  foo(s);    
  foo(std::move(s));

}



lvalue
lvalue
lvalue

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

解せないかもしれないが、仮引数の時は Hoge&&で受け取っていても左辺値参照になっている これを解決するためには、左辺値参照で入ってきた仮引数をふたたび右辺値参照にキャストする必要がある

先ほどの foo(string&&)からfoo2を呼ぶときに std::move()を使い右辺値参照にキャストすればよい

void foo( string&& s) {
  foo2(std::move(s));
};


rvalue
lvalue
rvalue

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

これで、仮引数の時は左辺値参照になっているというのが実証できた

とりあえず、ざっと一通りの右辺値参照の基礎をかいた 多分 この程度知っているという人が多いと思うので 読み飛ばしてください