std::moveにまつわる速度調査など stringとラムダ編

間違えている部分あると思うので
マサカリおねがいします！

そういえば ユニバーサル参照の説明してないわ、へんにstd::moveしてるわ、std::forwardしてないわ
説明として圧倒的に不足しているので
調査しなおして書き直します・・

#はじめに
この調査を行った経緯は、強い人が書いたコードの中に１つ下記の疑問コードがあったため
std::moveや右辺値参照を7割ほどしか理解していなかったため、具体的な速度調査を行ってみた

まず、前提として右辺値やstd::moveをある程度知っている必要があり、そのへんは先人たちが分かりやすい説明しているので
そっちで見ていただきたい
要約すると　右辺値は一時オブジェクト。アドレスが取れるかどうか　的な判断がわかりやすい
std::moveは、所有権を移動する関数ではなく、右辺値型へのキャストと覚えた方がわかりやすい

また、C++14以降でラムダキャプチャが強化され、moveキャプチャ等も出来るらしいので、今のうちに 右辺値参照はPerfectに理解しておきたい

##疑問コード

```cpp
  hoge( std::move( [](){ほげほげ;} ) );
```
元々 このラムダは右辺値なのに std::moveする必要あるんだろうか？
（結論は　無駄だと思う）

上記疑問コードの調査の前に変数（ここではstd::string）を使って調査してみる

#stringでの調査

##まず例文
```cpp
#include<iostream>
#include<chrono>

using namespace std;


template <typename Func>
 void time_elapsed(Func func, int count)
{
	auto t0 = std::chrono::high_resolution_clock::now();
	for (int n = 0; n < count; n++) {
		func();
	}
	auto t1 = std::chrono::high_resolution_clock::now();

	std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() << " msec" << std::endl;
}

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

void bar(string s) {
	 string hoge = s;
}
void bar(string &s) {
	 string hoge = s;
}
void bar(string &&s) {
	 string hoge = s;
}

auto main() -> int{
	time_elapsed([] {  foo(string("helo"));  }, 1000000);  // 起動直後の計測は捨てる（ゴミデータ）

	time_elapsed([] {  foo(string("helo"));  }, 1000000);
	time_elapsed([] {  foo1(string("helo"));  }, 1000000);  VCだとなぜか通る
	time_elapsed([] {  foo2(string("helo"));  }, 1000000);

	cout << endl;

	time_elapsed([] {  foo(std::move(string("helo")));  }, 1000000);   // Warning
	time_elapsed([] {  foo1(std::move(string("helo")));  }, 1000000);  // VCだとなぜか通る
	time_elapsed([] {  foo2(std::move(string("helo")));  }, 1000000);  // Warning

	cout << endl;

	static string cc("hoge");  // あえてキャプチャせずStaticに
	time_elapsed([] {  foo(cc);  }, 1000000);
	time_elapsed([] {  foo1(cc);  }, 1000000);
//	time_elapsed([] {  foo2(cc);  }, 1000000);	// 当然コンパイルエラー

	cout << endl;

	time_elapsed([] {  foo(std::move(cc));  }, 1000000);
	time_elapsed([] {  foo1(std::move(cc));  }, 1000000);  // VCだとなぜか通る
	time_elapsed([] {  foo2(std::move(cc));  }, 1000000);


}

結果 
呼び出される関数でstd::move しない（foo）
4961 msec
4958 msec
4948 msec

7249 msec
4964 msec
4952 msec

5315 msec
2675 msec

4889 msec
2533 msec
2528 msec

呼び出される関数でstd::move する（bar）
4654 msec
4653 msec
4627 msec

6931 msec
4673 msec
4655 msec

4979 msec
2329 msec

4614 msec
2353 msec
2379 msec
```

Wandboxがスポンサーゲットしてたので　張っておく！
https://wandbox.org/permlink/2gIjhKZgR59EsjaJ

VisualStudioとそれ以外でコンパイルエラーになるケースが違うが、Clangでエラーやワーニングが出るコードは書くべきではないと思う

一時オブジェクト（右辺値）引数、std::move(一時オブジェクト)、変数（左辺値）、std::move(変数)
に対してそれぞれ、オブジェクト受け、参照受け(&)、右辺値参照受け(&&)
の、合計12パターンをテストしてみた

##結果
上記をVisualStudioでビルドし、トレースおよびアセンブラで追っかけた結果、下記のパターンのグループが出来た

###一時オブジェクト作成
4650ms
foo(string("helo")); foo1(string("helo")); foo2(string("helo"));
foo1(std::move(string("helo"))); foo2(std::move(string("helo")));

```cpp
basic_string(_In_z_ const _Elem * const _Ptr)
foo内
basic_string(_Myt&& _Right) _NOEXCEPT
_Assign_rv_contents_with_alloc_always_equal(_STD move(_Right), _Use_memcpy_move{});
```

foo1(string("helo"))、foo1(std::move(string("helo")))
右辺値の参照(not 右辺値参照)はillformだと思ってたけどVCだと通るんだね？？　このあたり謎。

foo2(std::move(string("helo")))
一時オブジェクト(右辺値)をmoveするとWarning発生


###一時オブジェクト作成、右辺値参照
7000ms
foo(std::move(string("helo")))

```cpp
basic_string(_In_z_ const _Elem * const _Ptr)
basic_string(_Myt&& _Right) _NOEXCEPT
_Assign_rv_contents_with_alloc_always_equal(_STD move(_Right), _Use_memcpy_move{});
foo内
basic_string(_Myt&& _Right) _NOEXCEPT
_Assign_rv_contents_with_alloc_always_equal(_STD move(_Right), _Use_memcpy_move{});
```

Warning発生。一時オブジェクトをmoveすべきではない。
実行速度もとても遅いので使ってはいけない


###コピーコンストラクタ
4600ms
foo(cc)

```cpp
basic_string(const _Myt& _Right)
_Assign_lv_contents(_Right);
foo内
basic_string(_Myt&& _Right) _NOEXCEPT
_Assign_rv_contents_with_alloc_always_equal(_STD move(_Right), _Use_memcpy_move{});
```

foo(std::move(cc))

```cpp
basic_string(_Myt&& _Right) _NOEXCEPT
_Assign_rv_contents_with_alloc_always_equal(_STD move(_Right), _Use_memcpy_move{});
foo内
basic_string(const _Myt& _Right)
_Assign_lv_contents(_Right);
```

オブジェクトのコピーコンストラクトが行われるので当然おそい

###コンパイルエラー
foo2(cc)

オブジェクトから右辺値参照は出来ない


###処理なし
2330ms

foo1(cc)、foo1(std::move(cc))、foo2(std::move(cc))

```cpp
foo内
basic_string(_Myt&& _Right) _NOEXCEPT
_Assign_rv_contents_with_alloc_always_equal(_STD move(_Right), _Use_memcpy_move{});
```
通常の参照渡しも同じになるのは 受け関数でstd::moveしてるからのようだ
このコードが最も効率良いはずなので、常にこのコードをかきましょう！

##おまけ
受け取り側の関数で std::moveを使わない場合は　いずれのパターンも約300ms 遅くなった

```cpp
void foo(string s) {
	 string hoge = s;
}
```

##結論
まず　VisualStudioだとコンパイル出来てしまうコードがあったのは驚いた（コンパイルオプションとかあるのかな？）
ただ　いずれのパターンも書くべきでないしパフォーマンスも良くないので、正しい書き方をしましょう
私の考えでは　正解は１つじゃない。２つ。

### コピー
速度とかじゃなく、コピーを渡す必要があるシーンは出てくる
関数内で値を書き換えるが、それは困るというとき
そういう時はコストかかってもコピーするしかない

```cpp
hoge(string st){
  st +=" append string";
  std::cout << st << std::endl;
}
string foo("foo");
hoge(foo);
```


### 右辺値参照渡し  
参照渡しと同じように扱え 速度も最も早い
右辺値も渡せるので、参照渡しでは対応できない問題も解決
完全転送も可能なので、コピーする必要がなければ、常にこれを使うべきである
左辺値を渡そうとするとエラーになるので右辺値にキャストする必要がある（それがstd::move）
右辺値参照ではあるが、仮引数の時は左辺値なので、関数内でstd::moveしなければ通常の参照と動作はかわらない

```cpp
hoge(string &&st){
  string r(std::move(st));
  r +=" append string";
  std::cout << r << std::endl;
}

hoge(string("RV"));  // welform
string s("LV");
// hoge(s); // illform
 hoge(std::move(s)); // welform
```

もちろん、通常の参照時はオブジェクトの所有権を移動したくない場合は 関数をオーバーロードするとよい

```cpp

template<typename Val>
void foo(Val &s) {
	 Val hoge(s);
     cout << "ref: " << hoge << endl;
}

template<typename Val>
void foo(Val &&s) {
	 Val hoge(std::move(s));
     cout << "rvref: " << hoge << endl;
}


auto main() -> int{
    string s("helo");
    foo(string("helo")); // rval
    foo(s);              // lval
    foo(std::move(s));   // rval
    
    string s2("hi");
    foo(string(s2));     // rval
}
```




#いよいよラムダ
一言でいうと、関数（オブジェクト）も 一級オブジェクトなので、同じです。

##コード
```cpp

#include<iostream>
#include<chrono>

using namespace std;


template <typename Func>
 void time_elapsed(Func func, int count)
{
	auto t0 = std::chrono::high_resolution_clock::now();
	for (int n = 0; n < count; n++) {
		func();
	}
	auto t1 = std::chrono::high_resolution_clock::now();

	std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() << " msec" << std::endl;
}


 template <typename Func>
 void foo(Func rmd) {
	 rmd();
}

 template <typename Func>
 void foo1(Func& rmd) {
	 rmd();
 }
 template <typename Func>
 void foo2(Func&& rmd) {
	 rmd();
 }


auto main() -> int{
	time_elapsed([] {  foo([] {string s("abc"); });  }, 1000000);  // 起動直後の計測は捨てる（ゴミデータ）

	time_elapsed([] {  foo ([] {});  }, 10000000); 
	time_elapsed([] {  foo1([] {});  }, 10000000);  // VCではなぜかとおる
	time_elapsed([] {  foo2([] {});  }, 10000000); 

	
	time_elapsed([] {  foo (std::move([] {}));  }, 10000000);  // Warning
	time_elapsed([] {  foo1(std::move([] {}));  }, 10000000); // VCでは通る
	time_elapsed([] {  foo2(std::move([] {}));  }, 10000000);  // Warning


}

結果（ローカル）
63 msec
52 msec
53 msec
70 msec
71 msec
70 msec

備考として　foo側で std::move()を使いアクセス std::move(rmd)(); で下記の値
71 msec
69 msec
70 msec
88 msec
88 msec
86 msec
```

Wandbox
https://wandbox.org/permlink/LYAKIspu91nJtQv8


##考察
一級オブジェクトなので、ラムダ関数だろうが、右辺値（前回の string("hoge")）と同じなことはわかっている
が、オブジェクトに比べてほとんど差がない
ラムダを一度変数に入れ　static auto rm = []{}; それで渡しても　ほぼ同じ結果であった
ラムダの場合は 関数ポインタが渡される程度なんだろうか（ここは検証出来てないししたくない）

##結論
ラムダはコピーしても参照しても右辺値参照でも速度に差がなかった
VCだと微妙に std::moveすると遅くなったがclangでは差がなかったので
速度に関しては、考慮する必要がない

##正解はやはりオブジェクトと同じ
ラムダを呼び出し元でも使いまわすのならコピーする
参照を移しても問題なければ右辺値参照で渡せばいい
完全転送も可能なので、出来る限り右辺値参照を使うべきである