私は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リファレンスについて述べています。標準ですが、最後のものには当てはまりません!具体的には、さまざまな時点で、右辺値参照が左辺値にバインドできると述べています。これは、かつては真でしたが、変更されました(たとえば、int x; int && rrx = x; GCCでコンパイルされなくなりました)–drewbarbs2014年7月13日16:12
C ++ 03参照(現在はC ++ 11では左辺値参照と呼ばれています)の最大の違いは、定数でなくても一時的なもののように右辺値にバインドできることです。したがって、この構文は現在有効です。
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では、moveコンストラクターをオーバーロードできます。
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
ここで大きな違いに注意してください。moveコンストラクターは実際にその引数を変更します。これにより、一時的なものが構築中のオブジェクトに効果的に「移動」され、不要なコピーが排除されます。
移動コンストラクターは、一時的なものと、関数を使用して右辺値参照に明示的に変換される非定数左辺値参照に使用されstd::move
ます(変換を実行するだけです)。次のコードは、との両方のmoveコンストラクターを呼び出し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&
とっても、リテラル5にバインドされません。代わりにを使用することもできますが、コンストラクター引数を非const参照で取得するとどうなるでしょうか。真にジェネリックなファクトリ関数を作成するには、ファクトリを何度もオーバーロードする必要があります。工場で1つのパラメータタイプを使用する場合は問題ないかもしれませんが、追加のパラメータタイプごとに、必要なオーバーロードセットに2が掛けられます。これは非常に迅速に保守できません。foo
int
A1 const&
foo
A1&
A1 const&
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)));
}
これで、引数のrvalue / lvalue-nessは、T
のコンストラクターに渡されたときに保持されます。つまり、ファクトリが右辺値で呼び出された場合、T
のコンストラクタは右辺値で呼び出されます。ファクトリが左辺値で呼び出される場合、T
のコンストラクタは左辺値で呼び出されます。改善されたファクトリ関数は、1つの特別なルールのために機能します。
関数パラメーターの型がテンプレートパラメーターの形式
T&&
でT
あり、関数の引数が型の左辺値である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);
右辺値参照を示します。右辺値参照は、明示的に生成されない限り、一時オブジェクトにのみバインドされます。これらは、特定の状況下でオブジェクトをはるかに効率的にし、テンプレートコードを大幅に簡素化する完全転送と呼ばれる機能を提供するために使用されます。
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++03std::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&&
最も単純なテンプレート関数-最小値と最大値を考えてみましょう。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);
}
手元でどのように行われたか思い出せないため、リターンタイプの推論を省略しましたが、その最小値は、左辺値、右辺値、定数左辺値の任意の組み合わせを受け入れることができます。
T&&
型演繹(完全な転送など)で使用される場合の用語は、口語的に転送参照として知られています。「ユニバーサルリファレンス」という用語は、この記事でScott Meyersによって造られましたが、後で変更されました。
これは、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
};
コンストラクターと代入演算子は、右辺値参照をとるバージョンでオーバーロードされています。右辺値参照を使用すると、「左辺値または右辺値で呼び出されていますか?」という条件で、コンパイル時に(オーバーロード解決を介して)関数を分岐できます。これにより、リソースをコピーするのではなく移動する、より効率的なコンストラクターと代入演算子を上に作成することができました。
コンパイラーは、コンパイル時に(左辺値または右辺値のどちらに対して呼び出されているかに応じて)自動的に分岐し、ムーブコンストラクターまたはムーブ代入演算子のどちらを呼び出すかを選択します。
要約すると、右辺値参照により、移動セマンティクスが可能になります(および、以下の記事リンクで説明されている完全な転送)。
実用的なわかりやすい例の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);
多くの良い例とともに、これらすべてとそれ以上(右辺値が完全な転送を可能にする方法とその意味など)を説明する優れた記事は、ThomasBeckerのC++右辺値参照の説明です。この投稿は彼の記事に大きく依存していました。
短い紹介は、StroutrupらによるRvalue参照の簡単な紹介です。al