私はC ++ 11の新機能のいくつかを調査してきましたが、気付いたのは、変数の宣言における2つのアンパサンドT&& var
です。
まず、この獣は何と呼ばれていますか?Googleがこのような句読点を検索できるようにしてほしい。
正確にはどういう意味ですか?
一見、それは(Cスタイルのダブルポインターのような)二重参照のように見えますが、T** var
その使用例を考えるのに苦労しています。
私はC ++ 11の新機能のいくつかを調査してきましたが、気付いたのは、変数の宣言における2つのアンパサンドT&& var
です。
まず、この獣は何と呼ばれていますか?Googleがこのような句読点を検索できるようにしてほしい。
正確にはどういう意味ですか?
一見、それは(Cスタイルのダブルポインターのような)二重参照のように見えますが、T** var
その使用例を考えるのに苦労しています。
回答:
以下は、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左辺値参照に使用されます(変換を実行するだけです)。以下のコードの両方のための移動コンストラクタ呼び出しf1
とf2
:
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&
、foo
const以外の参照でコンストラクター引数を取る場合はどうなりますか?真に汎用的なファクトリ関数を作成するには、ファクトリを何度もオーバーロードする必要が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);
Named rvalue references are lvalues. Unnamed rvalue references are rvalues.
; これを知らずに、なぜ人々がT &&t; std::move(t);
ムーブアクターなどで長い間そうするのか理解するのに苦労しました。
int x; int &&rrx = x;
コンパイルされなくなりました)
typename identity<T>::type& a
同等ではありませんT&
か?
右辺値参照を示します。明示的に生成されない限り、右辺値参照は一時オブジェクトにのみバインドされます。これらは、特定の状況下でオブジェクトをより効率的にするため、およびテンプレートコードを大幅に簡略化する完全転送と呼ばれる機能を提供するために使用されます。
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つのタイプのフォームテンプレートをドロップすることをお勧めします。
T&&
型推論(完全転送など)と共に使用される場合の用語は、通称「転送参照」と呼ばれます。「ユニバーサルリファレンス」という用語は、この記事でスコットマイヤーズによって造語されましたが、後で変更されました。
これは、r値またはl値のいずれかであるためです。
次に例を示します。
// template
template<class T> foo(T&& t) { ... }
// auto
auto&& t = ...;
// typedef
typedef ... T;
T&& t = ...;
// decltype
decltype(...)&& t = ...;
より多くの議論は答えのために見つけることができます:ユニバーサル参照の構文
右辺値参照は、いくつかの例外を除いて、通常の参照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)
もコンテンツをコピーする必要があるのではないですか?「コピー代入演算子」についても同じ質問です。