C ++ 11でのT &&(二重アンパサンド)の意味は?


799

私はC ++ 11の新機能のいくつかを調査してきましたが、気付いたのは、変数の宣言における2つのアンパサンドT&& varです。

まず、この獣は何と呼ばれていますか?Googleがこのような句読点を検索できるようにしてほしい。

正確にはどういう意味ですか?

一見、それは(Cスタイルのダブルポインターのような)二重参照のように見えますが、T** varその使用例を考えるのに苦労しています。


55
これはc ++-faqに追加しました。これは、今後さらに増えると確信しています。
GManNickG 2011年


41
これを検索するには、googleを使用します。フレーズを引用符で囲むだけです。google.com / #q = " T%26%26 "に最初のヒットとして質問が表示されます。:)
sbi

ここに同様の質問に対する非常に良い、理解しやすい回答があります。stackoverflow.com
Daniel

2
Googleで「c ++ 2つのアンパサンドパラメータ」を検索するときに、3つのスタックオーバーフローの質問がありました。あなたの質問が最初の質問でした。したがって、「2つのアンパサンドパラメータ」を指定できる場合は、句読点を使用する必要もありません。
sergiol

回答:


668

右辺値参照を宣言します(標準提案ドキュメント)。

これが右辺値参照の紹介ですです。

以下は、Microsoftの標準ライブラリ開発者の1人による右辺値参照の詳細です。

注意: MSDNのリンクされた記事(「Rvalue参照:VC10のC ++ 0x機能、パート2」)は、Rvalue参照の非常に明確な紹介ですが、C ++ 11ドラフトでかつて真実であったRvalue参照について説明しています。標準ですが、最終版には当てはまりません!具体的には、さまざまな時点で、rvalueの参照がlvalueにバインドできることを示しています。これは、かつてはtrueでしたが変更されました(たとえば、int x; int && rrx = x;はGCCでコンパイルされません)– drewbarbs

C ++ 03参照(現在はC ++ 11では左辺値参照と呼ばれます)の最大の違いは、constである必要なく、一時的なように右辺値にバインドできることです。したがって、この構文は現在有効です。

T&& r = T();

右辺値参照は、主に以下を提供します。

セマンティクスを移動します。通常のconst-lvalue参照の代わりに右辺値参照を使用する移動コンストラクターおよび移動割り当て演算子を定義できるようになりました。移動はコピーのように機能しますが、ソースを変更せずに保持する必要はありません。実際、通常は、移動したリソースを所有しなくなるようにソースを変更します。これは、特に標準ライブラリの実装で、無関係なコピーを排除するのに最適です。

たとえば、コピーコンストラクタは次のようになります。

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;
}

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

moveコンストラクタは、一時std::move関数、および関数を使用して明示的に右辺値参照に変換される非const左辺値参照に使用されます(変換を実行するだけです)。以下のコードの両方のための移動コンストラクタ呼び出し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"

完璧な転送。右辺値参照により、テンプレート関数の引数を適切に転送できます。このファクトリ関数を例にとります:

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

呼び出したfactory<foo>(5)場合、引数はと推定されますint&。これは、fooコンストラクタがを受け取った場合でも、リテラル5にバインドされませんint。そうですね、代わりにを使用することもできますがA1 const&fooconst以外の参照でコンストラクター引数を取る場合はどうなりますか?真に汎用的なファクトリ関数を作成するには、ファクトリを何度もオーバーロードする必要がA1&ありA1 const&ます。ファクトリが1つのパラメータタイプをとる場合はそれで問題ないかもしれませんが、パラメータタイプを追加するたびに、必要なオーバーロードセットが2倍になります。

右辺値参照は、標準ライブラリがstd::forward左辺値/右辺値参照を適切に転送できる関数を定義できるようにすることで、この問題を修正します。の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)));
}

現在、引数の右辺値/左辺値は、Tコンストラクタに渡されても保持されます。つまり、factoryが右辺値で呼び出された場合、Tのコンストラクタは右辺値で呼び出されます。ファクトリが左辺値で呼び出された場合、Tコンストラクタは左辺値で呼び出されます。改善されたファクトリー機能は、1つの特別なルールにより機能します。

関数パラメーターの型がの形式T&&Tあり、がテンプレートパラメーターであり、関数の引数がtypeの左辺値であるA場合、型A&はテンプレート引数の推定に使用されます。

したがって、次のようにファクトリを使用できます。

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

重要な右辺値参照プロパティ

  • オーバーロードの解決では、左辺値は左辺値参照へのバインドを優先し、右辺値は右辺値参照へのバインドを優先します。したがって、一時がコピーコンストラクター/割り当て演算子よりも移動コンストラクター/移動割り当て演算子の呼び出しを好む理由。
  • 右辺値参照は、右辺値および暗黙的な変換の結果である一時変数に暗黙的にバインドします。つまりfloat f = 0f; int&& i = f;、floatは暗黙的にintに変換できるため、整形式です。参照は、変換の結果である一時的なものになります。
  • 名前付き右辺値参照は左辺値です。名前のない右辺値参照は右辺値です。 これはstd::move、次の場合に呼び出しが必要な理由を理解するために重要です。foo&& r = foo(); foo f = std::move(r);

65
+1 Named rvalue references are lvalues. Unnamed rvalue references are rvalues.; これを知らずに、なぜ人々がT &&t; std::move(t);ムーブアクターなどで長い間そうするのか理解するのに苦労しました。
legends2k 2013年

@MaximYegorushkin:その例では、rは純粋な右辺値(一時)にバインドしているため、一時変数の有効期間スコープを拡張する必要がありますか?
Peter Huene 2013

@PeterHuene私はそれを取り戻します、r値の参照は一時的なものの寿命を延長します。
Maxim Egorushkin 2013

32
注意:MSDN(「右辺値参照:C ++ 0xのがVC10、第2部で機能」)上のリンク先の記事である右辺値参照と非常に明確な導入、しかしだった右辺値参照に関する声明ます一度ドラフトC ++ 11で真を標準ですが、最終版には当てはまりません!具体的には、さまざまな時点で、右辺値参照は左辺値にバインドできると述べていますが、これはかつては真実でしたが変更されました(たとえば、GCCでint x; int &&rrx = x; コンパイルさなくなりました)
drewbarbs 14

@PeterHuene上記の例では、とtypename identity<T>::type& a同等ではありませんT&か?
ibp73

81

右辺値参照を示します。明示的に生成されない限り、右辺値参照は一時オブジェクトにのみバインドされます。これらは、特定の状況下でオブジェクトをより効率的にするため、およびテンプレートコードを大幅に簡略化する完全転送と呼ばれる機能を提供するために使用されます。

C ++ 03では、変更不可能な左辺値と右辺値のコピーを区別できません。

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);

C ++ 0xでは、これは当てはまりません。

std::string s;
std::string another(s);           // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);

これらのコンストラクタの背後にある実装を検討してください。最初のケースでは、文字列は値のセマンティクスを保持するためにコピーを実行する必要があり、これには新しいヒープの割り当てが含まれます。ただし、2番目のケースでは、コンストラクターに渡されたオブジェクトがすぐに破棄される予定であり、そのままにしておく必要がないことを事前に知っています。このシナリオでは、内部ポインターを効果的に交換するだけで、コピーをまったく実行できません。これにより、大幅に効率が向上します。移動セマンティクスは、内部で参照されるリソースのコピーが高価または禁止されているクラスに役立ちます。次のケースを考えてみましょう。std::unique_ptrこれで、クラスが一時的なものと非一時的なものを区別できるようになったので、移動セマンティクスを正しく機能させて、unique_ptrコピーできないが移動できるようにすることができます。つまり、std::unique_ptr標準コンテナに合法的に格納したり、ソートしたりできますが、C ++ 03 std::auto_ptrはそうではありません。

次に、右辺値参照の他の使用、完全転送を検討します。参照を参照にバインドする問題を考えてみましょう。

std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template

C ++ 03がこれについて何を言っているか思い出せませんが、C ++ 0xでは、右辺値参照を処理するときに結果として得られる型が重要です。タイプT(Tは参照タイプ)への右辺値参照は、タイプTの参照になります。

(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&

最も単純なテンプレート関数であるminとmaxを考えます。C ++ 03では、constとnon-constの4つの組み合わせすべてを手動でオーバーロードする必要があります。C ++ 0xでは、1つのオーバーロードにすぎません。可変テンプレートと組み合わせることで、完全な転送が可能になります。

template<typename A, typename B> auto min(A&& aref, B&& bref) {
    // for example, if you pass a const std::string& as first argument,
    // then A becomes const std::string& and by extension, aref becomes
    // const std::string&, completely maintaining it's type information.
    if (std::forward<A>(aref) < std::forward<B>(bref))
        return std::forward<A>(aref);
    else
        return std::forward<B>(bref);
}

戻り値の型の控除は、手作業で行われた方法を思い出せないため省略しましたが、そのminは、左辺値、右辺値、const左辺値の任意の組み合わせを受け入れることができます。


なぜ使用したのstd::forward<A>(aref) < std::forward<B>(bref)ですか?そして、私はあなたが前進しようとすると、この定義は正しいだろうと思ういけないint&float&。1つのタイプのフォームテンプレートをドロップすることをお勧めします。
ヤンクス2013

25

T&& 型推論(完全転送など)と共に使用される場合の用語は、通称「転送参照」と呼ばれます。「ユニバーサルリファレンス」という用語は、この記事でスコットマイヤーズによって造語されましたが、後で変更されました。

これは、r値またはl値のいずれかであるためです。

次に例を示します。

// template
template<class T> foo(T&& t) { ... }

// auto
auto&& t = ...;

// typedef
typedef ... T;
T&& t = ...;

// decltype
decltype(...)&& t = ...;

より多くの議論は答えのために見つけることができます:ユニバーサル参照の構文


14

右辺値参照は、いくつかの例外を除いて、通常の参照X&とほとんど同じように動作する型です。最も重要なのは、関数のオーバーロードの解決に関して、左辺値は古いスタイルの左辺値参照を好むが、右辺値は新しい右辺値参照を好むということです。

void foo(X& x);  // lvalue reference overload
void foo(X&& x); // rvalue reference overload

X x;
X foobar();

foo(x);        // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

それで、右辺値とは何ですか?左辺値ではないもの。左辺値は、メモリロケーションを参照し、&演算子を介してそのメモリロケーションのアドレスを取得できるようにする式です。

最初に右辺値が例で何を達成するかを理解することはほとんど簡単です:

 #include <cstring>
 class Sample {
  int *ptr; // large block of memory
  int size;
 public:
  Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} 
  {
     if (ptr != nullptr) memset(ptr, 0, sz);
  }
  // copy constructor that takes lvalue 
  Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
      nullptr}, size{s.size}
  {
     if (ptr != nullptr) memcpy(ptr, s.ptr, s.size);
     std::cout << "copy constructor called on lvalue\n";
  }

  // move constructor that take rvalue
  Sample(Sample&& s) 
  {  // steal s's resources
     ptr = s.ptr;
     size = s.size;        
     s.ptr = nullptr; // destructive write
     s.size = 0;
     cout << "Move constructor called on rvalue." << std::endl;
  }    
  // normal copy assignment operator taking lvalue
  Sample& operator=(const Sample& s)
  {
   if(this != &s) {
      delete [] ptr; // free current pointer
      size = s.size;

      if (size != 0) {
        ptr = new int[s.size];
        memcpy(ptr, s.ptr, s.size);
      } else 
         ptr = nullptr;
     }
     cout << "Copy Assignment called on lvalue." << std::endl;
     return *this;
  }    
 // overloaded move assignment operator taking rvalue
 Sample& operator=(Sample&& lhs)
 {
   if(this != &s) {
      delete [] ptr; //don't let ptr be orphaned 
      ptr = lhs.ptr;   //but now "steal" lhs, don't clone it.
      size = lhs.size; 
      lhs.ptr = nullptr; // lhs's new "stolen" state
      lhs.size = 0;
   }
   cout << "Move Assignment called on rvalue" << std::endl;
   return *this;
 }
//...snip
};     

コンストラクタと代入演算子は、右辺値参照を取るバージョンでオーバーロードされています。右辺値参照により、「左辺値または右辺値で呼び出されているか」という条件で、関数がコンパイル時に(オーバーロード解決を介して)分岐することができます。これにより、リソースをコピーするのではなく移動する、より効率的なコンストラクターと代入演算子を作成できました。

コンパイラーは、コンパイル時に自動的に分岐し(lvalueとrvalueのどちらで呼び出されるかに応じて)、移動コンストラクターと移動割り当て演算子のどちらを呼び出すかを選択します。

要約すると、右辺値参照は移動のセマンティクスを許可します(以下の記事のリンクで説明されている完全な転送)。

実用的で理解しやすい例の1つは、クラステンプレートstd :: unique_ptrです。unique_ptrは、基になるrawポインターの排他的な所有権を維持するため、unique_ptrはコピーできません。それは彼らの独占的所有権の不変に違反するでしょう。したがって、コピーコンストラクターはありません。ただし、移動コンストラクターはあります。

template<class T> class unique_ptr {
  //...snip
 unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};

 std::unique_ptr<int[] pt1{new int[10]};  
 std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.  

 // So we must first cast ptr1 to an rvalue 
 std::unique_ptr<int[]> ptr2{std::move(ptr1)};  

std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
 int size)      
{
  for (auto i = 0; i < size; ++i) {
     param[i] += 10;
  }
  return param; // implicitly calls unique_ptr(unique_ptr&&)
}

// Now use function     
unique_ptr<int[]> ptr{new int[10]};

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
           static_cast<unique_ptr<int[]>&&>(ptr), 10);

cout << "output:\n";

for(auto i = 0; i< 10; ++i) {
   cout << new_owner[i] << ", ";
}

output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 

static_cast<unique_ptr<int[]>&&>(ptr)通常はstd :: moveを使用して行われます

// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);

このすべてを説明する優れた記事(たとえば、右辺値が完全な転送を可能にする方法やその意味など)は、Thomas BeckerのC ++ Rvalue References Explainedで説明されています。この投稿は彼の記事に大きく依存していました。

短い紹介はStroutrupらによるRvalue 参照への簡単な紹介です。アル


コピーコンストラクタSample(const Sample& s)もコンテンツをコピーする必要があるのではないですか?「コピー代入演算子」についても同じ質問です。
K. Karamazen

はい、そうです。メモリのコピーに失敗しました。コピーコンストラクタとコピー代入演算子は、両方のそのサイズをテストした後のmemcpy(PTR、s.ptr、サイズ)行う必要があります= 0とサイズ= 0の場合、デフォルトのコンストラクタはmemsetの(PTR、0、サイズ)を行う必要があります!!
クルトkrueckeberg

わかりました、ありがとう。したがって、このコメントと前の2つのコメントは、問題が回答でも修正されているため、削除できます
K. Karamazen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.