KDPVの代わりに-実際のイベントに基づいて注目を集めるための短いドラマ。 これを安全にスキップして記事に移動できます。これは、右辺値リンクの理解、コンストラクターの移動、ユニバーサルリンク、完全な転送などに役立ちます。
3幕のドラマ
アクション1
コンパイラ スタックに住んでいるタイプTのローカルオブジェクトxは、残りの人生では使用しないという事実により、すべてのプロパティを奪取するように宣告されます。
オブジェクトx なに? 私はそこに一時的なオブジェクトではありません、私は永続的な登録を持っています、あなたには権利がありません!
コンパイラ 誰もあなたを追放しません。 しかし、標準コードの第11版によれば、すべてのものは、それらをさらに必要とする別のオブジェクトに転送されます。
オブジェクトx そして、どうやってそれをしますか? すべてのデータは安全にカプセル化されているため、だれもそれを不当に処理することはできません。 本当にそんなに必要な場合は、コピーコンストラクターにフラッシュドライブを付属させて、コピーします。
コンストラクター 。 長くて非効率的です。
オブジェクトx たぶん、あなたはreinterpret_castで私の窓を壊し、暗闇の中でmmきますか?
コンパイラ いいえ、あなたのケースはコレクターのサービスを使用するにはあまりにも普通です。 std :: move関数を呼び出すだけで、static_cast <T &&>が設定され、誰もが一時オブジェクト、つまり右辺値式であるとみなされます。
オブジェクトx それで、static_castは私に何の影響も与えません。 継承の最初のポイントに到達したらすぐに削除します。
コンパイラ 私は疑う余地はありませんが、これを行う前に、あなたはすでにドアの外で待っている運動コンストラクターに会います。 あなたは彼に会う時間がなかったようです...
アクション2
T(T &&) 。 こんにちは、一時オブジェクト!
オブジェクトx いやいやいや、私は左辺値です。
T(T &&) 。 誰もがそう言います。 さて、あなたは何を持っていますか? ここに来て...
第3幕(エピローグ)
T(定数T&) 私はあなたの暴力的な方法に満足していません。
T(T &&) 。 あなたは誇張し、自分で考えてください、死にかけているオブジェクトはなぜこのジャンクをすべて必要とするのですか? それが私にとってでなければ、長いコピーを作成し、デストラクタはオリジナルを破壊していたでしょう。 バカ。
T(定数T&) それで彼は嘘をつかなかったのかもしれません。
T(T &&) 。 それは私に違いはありません。 だから誰かがそれが必要だと決めた。 すべてが合法です、私はちょうど私の仕事をしています。
T(定数T&) しかし、以前は、あなたがいなくてもうまくいきました。 どういうわけか、彼らは転送と戻りを整理するか、動的メモリ内の生活空間をオブジェクトに割り当てました。 はい、コンパイラは最適化を支援しました。
T(T &&) 。 まあ、このすべての官僚主義を覚えておいてください。 オブジェクトが移入され、登録記録がすべて失われ、彼は最後までそこに住んでいたが、それを追い払うことはしなかった。 これらのオブジェクトをすでに後悔するのに十分な場合、そうでなければあなたは私の兄弟T(const T &&)に変わります-彼はさらに思いやりがあります。 私は彼に言いました:「それで、オブジェクトはもはやテナントではなく、彼の持ち物を持ちます」、そして彼はそれが不快だと言って、それをコピーしましょう。
T(定数T&) 私には、本当の凶悪犯である兄弟T(T&)もいます。 それは、コピーコンストラクタとして、私になりすまします... そして、怠を思いつきます。
まえがき
新しい概念は必ずしも頭にぴったり収まるとは限りません。 右辺値リンクで私に起こりました。 すべてが非常に単純で、その外観の前提条件は明確であるように見えますが、さまざまなテンプレートに包まれたさまざまな&&で飽和したコードを読み取ろうとすると、一般に何も理解していないことがわかります。
このトピックの研究における私の間違いは、右辺値リンクを根本的に新しいエンティティとして表したことです。 おそらく、これは奇妙に思えるかもしれません。すべてのマニュアルには、これは単なる右辺値への参照であると明確に書かれているからです。 わかった。 しかし、結局のところ、ユニバーサルリンクや完全な転送など、多くの新しい概念がそれらと共に登場しました。 また、&&を返す関数を呼び出すと、ある種の神秘的なxvalue式になります。 要するに、それらを通常のリンクと見なすのは簡単すぎるでしょう。
だから、最も重要なこと-複雑にしないでください! T&& ref = foo()
を見たが、現在refに関連付ける方法がわからない場合、右辺値への古き良き定数参照として扱います: const T& ref = foo()
、constなしのみ。
そして、なぜそれだけで右辺値へのリンクを取ることが許されなかったのでしょうか? そうしないと、式が左辺値であるか右辺値であるかに関する情報がすぐに失われます。 これで、右辺値は引数T &&で関数に渡され、左辺値はT&で渡されるようになります。 これにより、オブジェクトをさまざまな方法で処理できるようになります。これは、コピーおよび移動コンストラクターの実装にとって特に重要です。
私のもう1つの間違いは、Visual Studioの例を確認することです。 記事内の例、たとえばstd::string &str = std::string("42")
。これはコンパイルしないで、私と一緒にコンパイルします。 これは、Visual Studioの非標準の言語拡張機能によるものです。 VSが開発環境である場合、この動作を理解することは非常に重要であるため、これについて詳しく説明します。
この記事を読む最良の方法は、私を信じないで、自分ですべてをチェックすることです。 デバッグアセンブリでアドバイスします。 GCCを使用する場合-fno-elide-constructors
スイッチを-fno-elide-constructors
可能な限りコピーおよび移動コンストラクターの呼び出しを抑制するCopy Elisionテクニックをオフにすることをお勧めします。 VSの場合、非標準の拡張機能の使用をキャッチするために、第4レベルの警告をオンにします。
はじめに
右辺値リンクを学習し、コンストラクタを移動すると、多くの場合、同様の例に出くわすことがあります。
Matrix m = m1 * m2; std::string s = s1 + s2; std::vector<BigInt> primes = getAllMersennePrimes();
一時オブジェクトがコピーされ、すぐに破棄されます。 もちろん、これは明らかに冗長な操作であり、ムーブコンストラクターの動作はここではかなり明白です。 ただし、クラスに移動コンストラクターを追加すると、加速に気付かない場合があります。 結局、コンパイラーはさまざまな最適化手法、特にReturn Value Optimizationを使用します。これについては、記事の最後で少し説明します。 以下の例を提供します。 大きなローカルベクトルを塗りつぶしたと想像してください。
std::vector<int> items(1000000); fill(items);
メモリ内では、次のようになります(予約済みメモリの最後への3番目のポインタでスキームを再複雑化しないでください)。
そして、セッターを介してオブジェクトにそれを渡します。
storage->setItems(items); // items
以前と同じように、ベクトルは定数参照(左辺値と右辺値の両方を使用できるようにする)を介して渡され、コピーコンストラクターが呼び出され、同じ大きなベクトルが作成されました。 また、オリジナルはスコープを出た後にのみ削除されました。 新しいベクターにデータポインターを渡すだけです。
簡単です:
std::vector<int> items(1000000); fill(items); storage->setItems(std::move(items)); // items
そして、setItemsメソッドで:
Storage::setItems(std::vector<int> items) { items_ = std::move(items); }
ベクトルは値で渡されることに注意してください。これにより、左辺値のコピーと右辺値(特に一時オブジェクト)の移動が可能になります。 したがって、ローカルベクトルがまだ必要な場合は、std :: moveを使用せずにsetItemsを呼び出すだけです。 小さなオーバーヘッドは、引数が移動演算子を使用して再び移動されることです。
初期化
すべての概念に対処するために必要なことは、初期化のさまざまな方法に集中することだけです。 これは変数の初期化かもしれません:
T x = expr; // T x(expr);
引数を渡す:
void foo(T x); foo(expr);
戻り値:
T bar() { return expr; }
これらのすべてのケースは、意味的に同一であるため、初期化と呼ばれます。 次に、タイプT
検討しますT
次のいずれかになります。
- 参照なしタイプ(A)
- 左辺値参照(A&)
- 右辺値参照(A &&)
ポインターのある型はそれらの1つも指すことを明確にします。 たとえば、 A*
は参照なしのタイプ、 A*&
は左辺値参照などです。 次に、式expr
注意してください。 式には、値のタイプとカテゴリ(右辺値または左辺値)があります。 表現のタイプは、私たちが考えるほど重要ではありません。 このトピックでは、主な役割は式の値のカテゴリ(右辺値または左辺値)によって果たされます。簡略化のため、式のカテゴリと呼ばれます。
したがって、左側に3つのオプションがあり、右側に2つのオプションがあります合計6.より詳細に調べる前に、カテゴリの定義方法を学習します。
式の値のカテゴリ
左辺値と右辺値
C ++ 11より前は、これら2つのカテゴリのみが存在していました。 移動セマンティクスの出現により、右辺値カテゴリはさらに2つのカテゴリに分類されました。これについては、次のサブセクションで説明します。 したがって、各式のカテゴリは左辺値または右辺値です。 もちろん、標準はそれらのそれぞれに適用されるものを説明していますが、読むことは困難です。
スコットマイヤーズは次のルールを提供しています。
- 式のアドレスを取得できる場合、それは左辺値です。
- それ以外の場合、式の型が左辺値参照(つまり、T&またはconst T&など)である場合、それも左辺値です。
- それ以外の場合、式は右辺値です。
彼らは厳密ではないので、私は彼らが本当に好きではありません、そして時々変わる多くの微妙さがあります。 そして、最も重要なことは、トピックを勉強するとき、アドレスを取ることが可能かどうかをどのように理解できますか? まあ、まあ、私たちは一時オブジェクトができないことを知っています。 str
どうですか?
std::string &&str = std::string("Hello"); std::string *psrt = &str;
その型は右辺値参照ですが、 str
は左辺値であるため、可能です。 これは重要です。
必要に応じて、 cppreference:value categoryに目を向けます 。ここでは、式のリストが提供されます。 そこからいくつかの例:
- 左辺値:
- 変数または引数の名前(それらの型が右辺値参照であっても)。 たとえば、
std::cin
。 - 戻り値の型が左辺値参照である関数またはステートメントの呼び出し。 たとえば、
std::cout << 1
、++it
です。 -
"Hello, world!"
などの文字列リテラル 。
- 変数または引数の名前(それらの型が右辺値参照であっても)。 たとえば、
- 右辺値:
- 非文字列リテラル、例えば
42
、true
、nullptr
。 - 戻り値の型が参照ではない関数またはステートメントを呼び出す
- アドレスキャプチャ:
&a
。 - 戻り値の型が右辺値参照である関数またはステートメントの呼び出し。 たとえば、
std::move(x)
。 -
am
、ここでaは右辺値です。
- 非文字列リテラル、例えば
xvalue、prvalue、およびglvalue
既に述べたように、右辺値はxvalue(eXpiring左辺値)とprvalue(純粋な右辺値)の2つのカテゴリに分類されています。 そして、xvalueとともにlvalueはglvalue(一般化された左辺値)として知られるようになりました。 ここで、最高の精度を得るには、3つのカテゴリのいずれかに式を割り当てる必要があります。 図では、次のようになります。
次の式がxvalueに適用されます。
- 戻り値の型が右辺値参照である関数またはステートメントの呼び出し。 たとえば、
std::move(x)
。 -
am
、ここでaは右辺値です。
なぜ追加のカテゴリが必要なのですか? xvalue式は右辺値ですが、いくつかの左辺値プロパティがあります。たとえば、ポリモーフィックです。 さらに、これらの追加のカテゴリは必要ありませんが、白熱した議論の中で権限を高めるのに役立ちます。
初期化メソッド
推定では、6つのオプションが出てきました。 実際には、3つだけを考慮する必要があります。最初に、左辺値式で右辺値リンクを初期化できないためです。 そして次に、ある式によるオブジェクト(非参照型)の初期化は、コピーコンストラクターを使用して、または参照によって式を(コンストラクターに)渡すことによって移動します。 最初のガイドとして、そのようなスキーム:
T&x =左辺値
プレーンリンク。
T&x =右辺値
これは2つの場合にのみ機能します。
ケース1.一定の参照用
const T& x = rvalue;
c ++ 11より前は、これが一時オブジェクトをどこかに渡す唯一の方法でした。 たとえば、コピーコンストラクターの場合:
T::T(const T&); T x = T(); // T
または他の場所:
void readFile(const std::string &); std::string filename = "data"; readFile(filename + ".txt"); // std::string readFile
ご存知のように、定数参照は一時オブジェクトの寿命を延ばします。 はい。ただし、このリンクがまだスコープ内にある場合のみです。 一時オブジェクトはスタック上にあるため、スコープまたは関数を離れるとき、コンパイラーはオブジェクトの運命に責任を持つ必要がなくなります。
ケース2. Visual Studioで
Visual Studioで正常に動作する例に驚かないでください。
std::vector<int> &v = std::vector<int>({ 1, 2, 3 }); v.push_back(4);
答えは、警告レベル4でのみ表示されます。
warning C4239: nonstandard extension used note: A non-const reference may only be bound to an lvalue
この非標準のC ++拡張機能は/Za (Disable Language Extensions)
スイッチによって/Za (Disable Language Extensions)
になっていますが、 Windows.h
などの一部のヘッダーは拡張機能に含まれているため、コンパイルされません。
T && x =左辺値
簡単です。そのように書くことはできません。 注意してください、Habréには、「Rvalue Referencesの簡単な紹介」(2008年までの記事)の翻訳があります。これは、「rvalue」の検索エンジン結果の最初のものです。 そこからの例は間違っています:
A a; A&& a_ref2 = a; // rvalue
また、 std::move
誤った実装があります。 ただし、コメントはエラーを示していました。
T && x =右辺値
最も興味深いものに到達しました。 最も単純な例から始めましょう。
std::string foo(); std::string&& str = foo(); int&& i = 5;
これらのリンクは通常どおり動作します。 変更可能なconst T&リンクを想像するか、Visual Studio拡張機能を覚えておいてください。 問題が発生する可能性があります。なぜ、すべてのカテゴリの表現に通常のリンクを使用できないのですか? VSでは、これはうまく機能しました(クラス用)。 &&リンクを使用すると、式のタイプだけでなく、カテゴリ(左辺値または右辺値)によって関数、特にコンストラクターをオーバーロードできます。 2つのコンストラクターの例:
string(const string&); string(string&&); string s1 = "Hello "; string s2 = "world!"; string s3 = s1 + s2;
式s1 + s2
は右辺値であり、両方のコンストラクターがそれに適しています(セクションの冒頭の図を参照)。 タイプ文字列&&が優先されます。 移動コンストラクターに精通している人は、これが重要な理由を知っています。 これについて詳しく説明する前に、優先順位を理解します。
優先順位
ほとんどの場合、 T&&
「優先度T&&
高い」 const T&
ことを知るだけで十分です。 しかし、 const T&&
とT&
両方を扱うことをお勧めします。 拡張図は次のとおりです。
ルールは単純です(優先順位の降順):
- まず第一に、likeは好きになる傾向があります(水平矢印)。
- 定数オプションも適しています。
- そして、他のすべてのオプションが存在しない場合、
const T&
型はすべての式に適用されます。 - VSの場合:右辺値(破線矢印)への非定数リンクを忘れないでください 。
コンストラクタのコピーと移動(T x =左辺値; T x =右辺値;)
あなたが書くとき:
T x = expr; // T x(expr);
コンストラクターが呼び出されます。 どっち? クラスには複数のものがあります。
T(const T&); //copy- T(T&); // copy- T(const T&&); // ? T(T&&); //move-
コンストラクターが呼び出されると、式のカテゴリと引数のリンクのタイプに応じて、上記のスキームに従って参照によって式が転送されます。 次に、各コンストラクターの機能について説明します。
1. T(定数T&)
通常のコピーコンストラクタ。 優先度は最低ですが、任意の式を受け入れることができます。
2. T(T&)
最初は一定でなければソースを簡単に変更できるため、私はそれをitなコピーコンストラクタと呼びます。 通常のコピーコンストラクターよりも優先されます。 実際に使用されているとは思えません。 その使用例があれば、書いてください。
3. T(定数T &&)
これは右辺値式、つまり寿命が終了したオブジェクトを取ることができます。 死にかけているオブジェクトは言います:私は死にかけています 手g弾 データへのポインター、私はもはやそれらを必要としません。 そして、コンストラクターは答えます:いいえ、私はこれをすることができません、彼らはあなたのものであり、あなたと一緒にいます、私はコピーを作ることができるだけです。 また、私は実用的なユースケースを知りません。
4. T(T &&)
このコンストラクタは、定数でない右辺値に対してのみ呼び出されます。 このような式は、一時オブジェクトまたはstatic_cast<T&&>
を使用して右辺値参照にキャストされるオブジェクトです。 このような変換はオブジェクトにはまったく影響しませんが、このようなラッパー式は、右辺値参照によってmoveコンストラクター(または何らかの関数)に渡すことができます。 コンストラクターは、データおよびクラスの他のメンバーへのすべてのポインターを取得し、それらを新しいオブジェクトに渡します。 したがって、ムーブコンストラクターの効果的な作業には、クラスメンバーの数を減らす必要があります。 極端な場合、実装へのポインタは1つしか保存できません。 Pimplのイディオム(実装へのポインター)がここで適しています。
たとえば、ヒープ上のデータへのポインターと文字列の長さで指定された文字列のクラスを考えてみましょう(これは単なる概念的な例です)。
class String { char* data_; size_t length_; public: explicit String(size_t length) { data_ = static_cast<char*>(::operator new(length)); length_ = length; } ~String() { ::free(data_); } };
これは、移動コンストラクターのように見える場合があります。
String(String&& other) { data_ = other.data_; // length_ = other.length_; // other.data_ = nullptr; // ( ) other.length_ = 0; // }
データをリセットしなかった場合はどうなりますか(3-4コンストラクタ行)? 遅かれ早かれ、2つのダブルオブジェクトに対してデストラクタが呼び出され、同じデータを2回削除しようとします。 しかし、データポインタのみをリセットし、長さをリセットしないとどうなりますか? 結局、デストラクタは通常の2回動作します-特にこれは長さを使用しません。 その後、突然getLength
関数を使用するオブジェクトのユーザーは、誤った情報を受け取ります。 したがって、不要になったオブジェクトを野barに扱うことはできません。 いずれにしても、空のままにする必要がありますが、正しい状態にあります。 さらに、あなたは彼の生涯を通して数回動きを引き起こすことができます。
static_cast <T &&>およびstd :: move
移動コンストラクターについて議論するとき、すでにstatic_cast<T&&>
について話しstatic_cast<T&&>
。 思い出すと、 static_cast<T&&>
に「ラップ」された式は、オブジェクトを移動できることを示しています。 次に、新しいオブジェクトが初期化されると、コピーコンストラクターではなく、移動コンストラクターが呼び出されます。 これで、記事の最初からタスクを実装できます。
std::vector<int> items(1000000); fill(items); static_cast< std::vector<int>&& > (items); // , storage->setItems( static_cast< std::vector<int>&& > (items) ); // items
あなたはおそらくもっと便利な方法があることを知っているでしょう-関数std::move
呼び出す、これはstatic_castのラッパーです。 それがどのように機能するかを理解するために、私たちはそれを自分で書いてすべての熊手を踏みますが、これは有用な教訓になります。 さらに読む前に、自分で書くことができます。 もちろん、この関数は、異なるタイプを受け入れるために定型である必要があります。 しかし、今のところ、簡単にするために、特定のクラスAに対して1つを行います。推測します。 左辺値を渡したい場合、これはタイプA&の引数を使用してのみ実行できます。 次に、これをA &&に変換し、戻り値の型もA &&になります。 &&を返す関数の呼び出しは、必要に応じて右辺値式(より正確にはxvalue)です。
A&& my_move(A& a) { return static_cast<A&&>(a); }
しかし、標準関数std :: moveは右辺値も受け入れるため、普遍的であり、私たちのものとは言えません。 1つの解決策は簡単です。引数A &&を使用して別の関数を追加します。
A&& my_move(A&& a) { return static_cast<A&&>(a); }
左辺値と右辺値の両方で機能します。 ただし、実際のstd :: moveは1つだけで、テンプレート引数の型はT &&です。 どうして? さらに理解します。
リンク圧縮とユニバーサルリンク
そのため、定型文std :: moveは引数T &&の左辺値を取ることがわかりました。 したがって、T &&は右辺値参照ではありません。 T &&はどういうわけかT&に変わっています。 しかし、Tはどのような型でインスタンス化されますか? 何が何であるかを理解するために、式の2つのカテゴリ(右辺値と左辺値)に対して2つのオーバーロード関数を呼び出してみましょう。
template<class T> void foo(T&); template<class T> void foo(T&&); A a; foo(a); //lvalue; foo(A()); //rvalue;
関数自体で、Tがインスタンス化されるタイプを確認します。リンクのタイプは次のように確認できます。
bool is_lvalue = std::is_lvalue_reference<T>::value; bool is_rvalue = std::is_rvalue_reference<T>::value;
3つのオプションが適切であることがわかります。
-
foo(T&)
呼び出すときの表記foo(lvalue)
は、foo<T>(lvalue)
と同等です。 -
foo(T&&)
呼び出すときの表記foo(rvalue)
は、foo<T>(rvalue)
と同等です。 -
foo(T&&)
呼び出すときの表記foo(lvalue)
は、foo<T&>(lvalue)
と同等です。
次の図では、矢印の署名は、どのタイプTがインスタンス化されたかを示しています。
最初の2つのオプションは予測可能であり、それらに依存していました。 3つ目は、参照折りたたみルールを使用します。リンク圧縮は、リンクへのリンクが表示されるときの動作を決定します。 このようなデザイン自体は禁止されていますが、テンプレートで発生します。 したがって、TがA &&によってインスタンス化される場合、T &&(A && &&)= A &&であり、他の場合(リンクへのリンク)のタイプはA&であることが示されました。
T = A& => T& = A& T = A& => T&& = A& T = A&& => T& = A& T = A&& => T&& = A&&
したがって、タイプT &&の引数(Tは総称タイプ)は、両方のタイプのリンクを受け入れることができます。 したがって、T &&などのリンクは、 ユニバーサルリンクと呼ばれます。
std :: move(続き)
std :: moveでT &&を使用できることは明らかです(上の図のfoo(T &&)を参照)。 my_moveにステレオタイプを追加しましょう。
template<class T> T&& my_move(T&& t) { return static_cast<T&&>(t); }
T &&は左辺値のA&に変わるため、これは誤った実装です。この場合、関数インスタンスは次のようになります。
A& my_move(A& t) { return static_cast<A&>(t); }
引数の型は正しいですが、他の場所ではA&ではなくA &&を残す必要がありました。 std :: moveの実装を見る時間です:
template<class T> typename remove_reference<T>::type&& move(T&& _Arg) { return (static_cast<typename remove_reference<T>::type&&>(_Arg)); }
明確だと思います:remove_referenceはすべてのリンクを削除し、結果の型に&&が追加されます。 ところで、remove_referenceクラスは非常に単純です。 これらは、パラメーターT、T&、T &&を持つ3つのテンプレートクラスの特殊化です。
template<class T> struct remove_reference { typedef T type; }; template<class T> struct remove_reference<T&> { typedef T type; }; template<class T> struct remove_reference<T&&> { typedef T type; };
完全転送と標準::転送
すべての問題を整理したように見えますが、そうではありません。 T &&テンプレートに従ってすべての表現を転送することを学びましたが、それらをさらに操作する方法にはまだ興味がありませんでした。 ためらうことなくすべてを右辺値に導くため、std :: move関数は考慮されません。 式のカテゴリを区別する必要があるのはなぜですか? make_shared
類似物を書いていると想像してmake_shared
。 make_shared<T>(...)
自体が、指定された引数でコンストラクタTを呼び出すことを思い出します。 今は可変長テンプレートを扱う時ではありません。そのため、簡単にするために、引数が1つしかないと仮定します。 また、最も一般的なポインターを返します。 簡単なクラスを取ります:
class A { public: A(); // A(const A&); // A(A&&); // };
これを行いたい:
A a; A* a1 = make_raw_ptr<A>(a); // A* a2 = make_raw_ptr<A>(A()); // delete a1; delete a2;
リンク圧縮を使用して、実装を記述します。
template<class T, class Arg> T* make_raw_ptr(Arg &&arg) { return new T(arg); };
もちろん、うまくいきます。 常にコピーのみが発生します。 間違いを見つけましたか? 単純です-argは常に左辺値です。 元の表現のカテゴリに関する情報を失ったようです。 そうではありません-それはまだArgから抽出できます。 実際、左辺値の場合:Arg = A&、右辺値の場合:Arg = A リンクのタイプを復元する関数が必要であることがわかりました。つまり、次のとおりです。
- タイプA&の左辺値を渡すと、A&を返します
- タイプAの左辺値を渡すと、A &&を返します
- 右辺値を渡すとき...気になるまで。
A ( ):
// 1- A& my_forward(A& a) { return a; } // 2- A&& my_forward(A& a) // , lvalue { return static_cast<A&&>(a); //, rvalue }
, . , A&, A. , A& — A&, A — A&&. , "" &&.
template<class T> T&& my_forward(T& a) { return static_cast<T&&>(a); } template<class T, class Arg> T* make_raw_ptr(Arg &&arg) { return new T(my_forward<Arg>(arg)); };
std::forward . & &&, .
/ , , . move-. . GCC -fno-elide-constructors
. , .
.
T foo() { T result; return result; } foo();
foo . , . , , :
T temp = result; //
, , . temp — , . , result, , rvalue, foo. temp . , ( & &&) .
, :
T x = foo();
:
T temp = result; // T x = std::move(temp); //
. , .
, ( , sizeof, ), .
Copy elision return value optimization
C++ : ? std::vector<int> get();
void get(std::vector<int> &);
move- . copy elision — , . , return value optimization (RVO). ( ), , . , , . RVO? Visual Studio 2015:
T foo() { return T(); } class T { public: T() = default; //RVO T() {}; // };
, . , , . std::shared_ptr std::unique_ptr.