1. ホーム
  2. c++

[解決済み] C++11のT&&(ダブルアンパサンド)の意味とは?

2022-03-17 09:46:16

質問

C++11の新機能を調べていて気づいたのは、変数を宣言するときにアンパサンドを2つ使うことです。 T&& var .

とりあえず、この獣はなんという名前なのだろうか?Googleでこんな句読点の検索ができるようになればいいのに。

具体的にはどのようなものかというと の意味は?

一見すると、二重参照(C言語のダブルポインタのようなもの)のように見えます。 T** var ) が、そのユースケースが思い浮かばないのです。

どのように解決するのか?

を宣言しています。 rvalue リファレンス (規格提案書doc)。

ここでは、rvalueについて紹介します。 リファレンス .

Microsoftの標準ライブラリの1つで、rvalue参照について深く掘り下げた素晴らしい記事があります。 開発者 .

注意 MSDN のリンク先の記事("Rvalue References: C++0x Features in VC10, Part 2")は、Rvalue 参照について非常にわかりやすく紹介していますが、Rvalue 参照について、かつて C++11 標準草案では正しかったが、最終的には正しくないという記述をしています! 具体的には、rvalue参照はlvalueにバインドできると様々な箇所で述べており、これはかつて真実であったが、変更された。(例: int x; int &&rrx = x; もはやGCCではコンパイルできない) - drawnbarbs Jul 13 '14 at 16:12

C++03の参照(C++11ではlvalue参照と呼ばれるようになった)の最大の違いは、constでなくてもtemporaryのようにrvalueにバインドできることです。 したがって、この構文が合法になりました。

T&& r = T();

rvalue参照は、主に次のようなものを提供する。

移動セマンティクス . 通常の const-lvalue 参照の代わりに rvalue 参照を取る移動コンストラクタと移動代入演算子が定義できるようになりました。 移動はコピーと同様に機能しますが、移動元を変更しないことが条件ではありません。実際、通常は移動元を変更して、移動したリソースを所有しないようにします。 これは、特に標準的なライブラリ実装において、余計なコピーを排除するのに適しています。

例えば、コピーコンストラクタは次のようなものである。

foo(foo const& other)
{
    this->length = other.length;
    this->ptr = new int[other.length];
    copy(other.ptr, other.ptr + other.length, this->ptr);
}

このコンストラクタにテンポラリが渡された場合、テンポラリは破棄されることが分かっているので、コピーは不要になります。 C++03 では、テンポラリを渡されたかどうかを判断できないため、コピーを防止する方法はありません。 C++11では、移動コンストラクタをオーバーロードすることができます。

foo(foo&& other)
{
   this->length = other.length;
   this->ptr = other.ptr;
   other.length = 0;
   other.ptr = nullptr;
}

ここで大きな違いに注目してください。移動コンストラクタは実際に引数を変更します。 これにより、一時的なオブジェクトを構築中のオブジェクトに効果的に移動させ、不要なコピーを排除することができます。

移動コンストラクタはテンポラリや、非恒等式の l 値参照から、明示的に std::move 関数を使用します(変換を行うだけです)。 次のコードは、どちらも移動コンストラクタを起動して f1f2 :

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

パーフェクトフォワーディング rvalue参照により、テンプレート化された関数の引数を適切に転送することができます。 例えば、このファクトリー関数を考えてみましょう。

template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
    return std::unique_ptr<T>(new T(a1));
}

を呼び出すと factory<foo>(5) であることが推論されます。 int& を指定しても、リテラル5にはバインドされません。 foo のコンストラクタは int . さて、代わりに A1 const& しかし、もし foo はコンストラクタの引数を非恒等式参照で取るのでしょうか? 真に汎用的なファクトリー関数を作るには、ファクトリーをオーバーロードして A1&A1 const& . しかし、パラメータが1つ増えるごとに、必要なオーバーロードが2倍になってしまいます。

rvalue参照は、標準ライブラリで std::forward 関数で、lvalue/rvalue 参照を適切に転送することができます。 の詳細については std::forward の動作は この素晴らしい回答 .

これにより、次のようにファクトリー関数を定義することができます。

template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
    return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}

これで、引数のrvalue/lvalueらしさが保たれたまま T のコンストラクタを使用します。 つまり、factoryがrvalueで呼び出された場合。 T のコンストラクタがr値で呼び出されます。 もしfactoryがl値で呼ばれた場合。 T のコンストラクタは lvalue で呼び出されます。 このように改良されたファクトリー機能は、ある特別なルールによって機能しています。

<ブロッククオート

関数パラメータの型が 形式 T&& ここで T はテンプレート パラメータ、そして関数の引数 の型の lvalue です。 A の場合、その型は A& は はテンプレート引数の推論に使用されます。

よって、factoryはこのように使うことができる。

auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1);   // calls foo(foo const&)

重要な rvalue リファレンスプロパティ :

  • 過負荷解消のため。 lvalues は lvalue 参照へのバインディングを好み、rvalues は rvalue 参照へのバインディングを好みます。 . そのため、一時演算子はコピー演算子や代入演算子よりも、移動演算子や移動代入演算子を呼び出すことを好むのです。
  • rvalue参照は、rvalueおよび暗黙の変換の結果であるtemporaryに暗黙のうちにバインドされます。 ... すなわち float f = 0f; int&& i = f; は、floatがintに暗黙のうちに変換されるため、うまく形成されています。この参照は、変換の結果である一時的なものです。
  • 名前付きrvalueの参照はlvalueです。 名前のないrvalue参照はrvalueです。 の理由を理解する上で重要です。 std::move の呼び出しが必要です。 foo&& r = foo(); foo f = std::move(r);