関数テンプレートは暗黙的インスタンス化できる
で終わりなんだけど、少し悩んでやっぱ無理そうだったのでメモ 関数テンプレートで実装出来るのだが、使い勝手や最適化など考えて 関数オブジェクトにしたかったが 関数とクラスの テンプレートの違いで出来なかった
追記:やっぱできたぜ。メンバをテンプレートにすればOK
クラスのコンストラクタを可変長テンプレートにして ごにょごにょ - ムラサメ研究ブログ
テンプレート関数は暗黙的インスタンス化可能です!
template<T> void hoge(T t){}; hoge(12);
可能というかむしろ、暗黙的に使うよね。
もちろん あえて明示的にも書ける
template<T> void hoge(T t){}; hoge(12);
クラスは暗黙的インスタンス化できない
#include<iostream> template <class T> struct Stats{ void operator()(T t){} const; }; int main() { Stats<int>()( 12 ); // Stats()( 12 ); // 暗黙的インスタンス化できない }
右辺値参照~完全転送まで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関数で処理できる
とてもすばらしい。
右辺値参照~完全転送まで100%理解するぞ! part5 参照の圧縮(reference collapsing)
右辺値参照の山場の一つ Universal Referenceまであと一歩
参照の圧縮とは
UniversalReferenceそのものだが
参照は(const等のストレージクラス除き)2種類しかない
X& (左辺値参照)、X&&(右辺値参照)
X&&& 等はエラーですし X& && 等、参照を重ねる事は出来ない
でも、templateを使うと不運にも参照が重なる事がある
template<class T> void hoge(T& t){ }
例えばこのようなテンプレートに X&型の変数を適用したら T&は X& & になり、参照が重なってしまう これらの参照が重なった時はどうなるのだろうか?
コンパイルエラーにすると、テンプレートに参照を渡すものがほとんど動かなくなる C++では当然 参照が重なった場合に明確な法則がある
#include <iostream> using namespace std; class X{ public: X(){ } X(X& x){ cout << "copy" << endl; } X(X&& x){ cout << "move" << endl; } }; int main() { using Y=X&; using Z=X&&; X x; X( (Y&)x ); X( (Y&&)x ); X( (Z&)x ); X( (Z&&)x ); } ---- copy copy copy move
template 以外に typedef (using) でも参照を重ねる事が出来るので 試してみた
結果は X& & -> X&
X& && -> X&
X&& & -> X&
X&& && -> X&&
と、結論からいえば すべて右辺値参照の時のみ右辺値参照。それ以外は左辺値参照となる
このことを 参照の圧縮(reference collapsing) と呼ぶ
右辺値参照~完全転送まで100%理解するぞ! part4 Universal Reference その前に
こうやって 人に見せようと書く事は自分の理解になる
復習DEATH
前回の復習。右辺値参照を関数の引数にするには 一見コピーに見えるが void hoge( X x ) とプロトタイプすればよい 呼び出し側で hoge( std::move(x) ) とすればムーブコンストラクタが使われる という事 で 復習
#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(){ cout << "destuct" << endl; } }; void hoge(X x){ cout << "hoge(X)" << endl; } int main() { { X x; hoge( x ); } cout << "____" << endl; { const X x; hoge( x ); } cout << "____" << endl; { X x; hoge( std::move(x) ); } cout << "____" << endl; { hoge( X() ); } cout << "____" << endl; { hoge( std::move(X()) ); } } constuct copy hoge(X) destuct destuct ____ constuct const copy hoge(X) destuct destuct ____ constuct move hoge(X) destuct destuct ____ constuct hoge(X) destuct ____ constuct move hoge(X) destuct destuct
std::move() で呼ぶとムーブされ、何もつけないとコピーされる。正しい使い方だ。 少し気になるのは X() と右辺値で直接呼んだ場合は一時オブジェクトが生成されず効率的だが std::move( X() ) とすると、一時オブジェクトを生成し、それを右辺値にキャストしムーブコンストラクタが発生する (最適化で このへんは最適化されると思うけど、実装依存なので 可能なら コードで最適化しておきたい)
本題
上のコードで納得いかないところ オブジェクトの参照だけで良い時にも無駄にオブジェクトのコピーが走る 納得いかないので 調査をする
前回は一緒に受け取るようにした 参照、const参照、右辺値参照に分割してみる (先にこちらから説明すべきであった 反省)
#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(){}; }; static X xx; /* void hoge(X x){ xx = x; cout << "hoge(X)" << endl; } */ void hoge(X& x){ xx = x; cout << "hoge(X&)" << endl; } void hoge(const X& x){ xx = x; cout << "hoge(const X&)" << endl; } void hoge(X&& x){ xx = std::move(x); cout << "hoge(X&&)" << endl; } int main() { cout << "____" << endl; { X x; hoge( x ); x.foo(); } cout << "____" << endl; { const X x; hoge( x ); } cout << "____" << endl; { X x; hoge( std::move(x) ); } cout << "____" << endl; { hoge( X() ); } cout << "____" << endl; { hoge( std::move(X()) ); } cout << "____" << endl; }
面倒なので static変数にコピーしているが、普通はクラスのメンバに代入するだろう (explicit付けたよ! 前回の例では つけられないが ムーブコンストラクタにはexplicit付けるべき)
左辺値参照が引数に入ってきたら、中身を自分にコピー 右辺値参照であればムーブ
バッチリ!!
ところがこれを前回のように hoge(X x) で両方を受け取ると困った事になる
xx = x; と書けば ムーブ出来る時でもコピーになるし xx = std::move(x); では、常にムーブになる
ではやはり、 参照、const参照、右辺値参照 と常に3種類の関数が必要なのか??? また 引数がふえれば 6種類、9種類・・・ 組み合わせ爆発してしまう
そこで登場するのが Universal Reference
次回ご期待!
右辺値参照~完全転送まで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) 関数を呼ぶときに 右辺値であればムーブ、左辺値であればコピーされる コピーしたくない時は、呼ぶときに ムーブセマンティクス忘れないように!
右辺値参照~完全転送まで100%理解するぞ! part2 VisualStudioの罠
はじめに
おまけ。 右辺値参照を理解するうえで大きな妨げになった VisualStudioでの挙動(バグ?? 詳細希望)
左辺値は暗黙で右辺値参照にキャスト出来ない
バグ? の前に基礎から 左辺値(参照)は 左辺値にのみ 右辺値(参照)は 右辺値にのみ 暗黙キャストがゆるされる
#include<iostream> void foo1(int& i) {} void foo2(int&& i) {} auto main() -> int{ int i; int &i1(i); // lvalue -> lvalue ref well-formed int &i2(i1); // lvalue ref -> lvalue ref well-formed // int &i3(1); // rvalue -> lvalue ref ill-formed int &&ii(1); // rvalue -> rvalue ref well-formed // int &&ii2(ii); // lvalue ref -> rvalue ref ill-formed iiは右辺値を代入しているがそれ自体は左辺値(アドレスが存在する) int &&ii3(std::move(ii)); // rvalue ref -> rvalue ref well-formed foo1(i); // lvalue -> lvalue ref well-formed foo1(i1); // lvalue ref -> lvalue ref well-formed // foo1(1); // rvalue -> lvalue ref ill-formed foo2(1); // rvalue -> rvalue ref well-formed // foo2(ii); // lvalue ref -> rvalue ref ill-formed iiは右辺値を代入しているがそれ自体は左辺値(アドレスが存在する) foo2(std::move(ii)); // rvalue ref -> rvalue ref well-formed }
上記の通り、右辺値参照と左辺値参照は 暗黙で変換できないので std::moveでキャストする必要がある
VCの不具合??
stringやvector等で試すと不思議なことがおこった
#include<iostream> void Str(std::string& s) { } auto main() -> int{ Str( std::string("right value")); // rvalue -> lvalue ref ill-formだがVisualStudioでは通る・・ }
clanやgccで試したが 当然エラーになるし VisualStudioでも int等で試すとエラーになるが stringやvector等のコンテナだと いずれも上記のコードが通るので 移植性を考える場合は 上記のコードを書かないようにすべきと思われる
右辺値参照~完全転送まで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
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
解せないかもしれないが、仮引数の時は Hoge&&で受け取っていても左辺値参照になっている これを解決するためには、左辺値参照で入ってきた仮引数をふたたび右辺値参照にキャストする必要がある
先ほどの foo(string&&)からfoo2を呼ぶときに std::move()を使い右辺値参照にキャストすればよい
void foo( string&& s) {
foo2(std::move(s));
};
rvalue
lvalue
rvalue
これで、仮引数の時は左辺値参照になっているというのが実証できた
とりあえず、ざっと一通りの右辺値参照の基礎をかいた 多分 この程度知っているという人が多いと思うので 読み飛ばしてください