rvalue 参照宣言子: &&
右辺値の式への参照を保持します。
構文
rvalue-reference-type-id
:
type-specifier-seq
&&
attribute-specifier-seq
optptr-abstract-declarator
opt
解説
右辺値参照を使用すると、左辺値を右辺値と区別できます。 lvalue 参照と rvalue 参照は構文的および意味的に似ていますが、従う規則が少し異なります。 左辺値および右辺値の詳細については、左辺値と右辺値に関する記事を参照してください。 左辺値参照の詳細については、「Lvalue 参照宣言子: >」を参照してください。
以降のセクションでは、右辺値参照がどのように "移動セマンティクス" と "完全転送" の実装をサポートするかについて説明します。
移動セマンティクス
右辺値参照では、アプリケーションのパフォーマンスを大幅に向上させることができる "移動セマンティクス" の実装をサポートします。 移動セマンティクスにより、オブジェクト間でリソース (動的に割り当てられるメモリなど) を転送するコードを記述できます。 移動セマンティクスが機能するのは、プログラム内の他の場所からは参照できない一時オブジェクトからリソースを転送できるようになるためです。
移動セマンティクスを実装する場合、通常はクラスに対して "移動コンストラクター" と、必要に応じて移動代入演算子 (operator=
) を指定します。 その後は、ソースが右辺値であるコピーおよび代入演算では、移動セマンティクスが自動的に利用されます。 既定のコピー コンストラクターとは異なり、コンパイラでは既定の移動コンストラクターを提供しません。 移動コンストラクターを記述して使用する方法について詳しくは、移動コンストラクターと移動代入演算子に関するページを参照してください。
移動セマンティクスを活用するために、通常の関数と演算子をオーバーロードすることもできます。 Visual Studio 2010 には、C++ 標準ライブラリへの移動セマンティクスが導入されています。 たとえば、string
クラスでは、移動セマンティクスを実行する演算を実装しています。 複数の文字列を連結し、結果を出力する次の例を考えます。
// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = string("h") + "e" + "ll" + "o";
cout << s << endl;
}
Visual Studio 2010 より前では、operator+
を呼び出すたびに、新しい一時 string
オブジェクト (rvalue) が割り当てられて返されます。 operator+
では、ソース文字列が lvalue であるか rvalue であるかを判別できないため、ある文字列を他の文字列に追加できません。 ソース文字列が両方とも左辺値である場合は、プログラム内の別の場所で参照される可能性があるため、変更しないでください。 右辺値を取得するように変更 operator+
するには、右辺値参照を使用します。これはプログラム内の他の場所では参照できません。 このように変更すると、operator+
ではある文字列を別の文字列に追加できるようになります。 この変更によって、string
クラスで実行する必要がある動的メモリ割り当ての数が大幅に減ります。 クラスの詳細については、「クラス」をstring
参照してくださいbasic_string
。
また、コンパイラで戻り値の最適化 (RVO) または名前付き戻り値の最適化 (NRVO) を使用できない場合に、セマンティクスの移動が役立ちます。 このような場合、型が移動コンストラクターを定義していれば、コンパイラはその移動コンストラクターを呼び出します。
移動セマンティクスをより深く理解するために、vector
オブジェクトに要素を挿入する例について考えてみましょう。 オブジェクトの容量を vector
超えた場合、 vector
オブジェクトは要素に対して十分なメモリを再割り当てしてから、各要素を別のメモリ位置にコピーして、挿入された要素のスペースを作成する必要があります。 挿入操作で要素をコピーすると、最初に新しい要素が作成されます。 次に、コピー コンストラクターを呼び出して、前の要素から新しい要素にデータをコピーします。 最後に、前の要素を破棄します。 セマンティクスの移動を使用すると、負荷の高いメモリ割り当てとコピー操作を実行する必要がなく、オブジェクトを直接移動できます。
vector
の例で移動セマンティクスを利用するには、オブジェクト間でデータを移動する移動コンストラクターを記述します。
Visual Studio 2010 の C++ 標準ライブラリへの移動セマンティクスの導入の詳細については、C++ 標準ライブラリに関する記事を参照してください。
完全な転送
完全転送により、オーバーロード関数の必要性が低くなり、転送の問題を回避できます。 "転送の問題" は、参照をパラメーターとして受け取るジェネリック関数を記述するときに発生する可能性があります。 これらのパラメーターを別の関数に渡す (または "転送する") 場合 (たとえば、const T&
型のパラメーターを受け取る場合)、呼び出された関数で、そのパラメーターの値を変更することはできません。 ジェネリック関数で T&
型のパラメーターを受け取る場合は、rvalue (一時オブジェクトや整数リテラルなど) を使用して関数を呼び出すことはできません。
通常、この問題を解決するには、各パラメーターに T&
と const T&
の両方を受け取る、ジェネリック関数のオーバーロードされたバージョンを用意する必要があります。 その結果、オーバーロードされた関数の数は、パラメーターの数と共に指数関数的に増加します。 rvalue 参照を使用すると、任意の引数を使用できる 1 つのバージョンの関数を記述できます。 その後、その関数では、他の関数が直接呼び出された場合と同様に、それらを別の関数に転送できます。
W
、X
、Y
、および Z
の 4 つの型を宣言する次の例を考えます。 各型のコンストラクターでは、const
および非 const
左辺値参照の異なる組み合わせをパラメーターとして受け取ります。
struct W
{
W(int&, int&) {}
};
struct X
{
X(const int&, int&) {}
};
struct Y
{
Y(int&, const int&) {}
};
struct Z
{
Z(const int&, const int&) {}
};
オブジェクトを生成するジェネリック関数を記述するとします。 次の例は、この関数を記述する方法の 1 つを示しています。
template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
return new T(a1, a2);
}
次の例は、factory
関数への有効な呼び出しを示しています。
int a = 4, b = 5;
W* pw = factory<W>(a, b);
ただし、次の例には、factory
関数の有効な呼び出しは含まれていません。 これは、パラメーターとして変更可能な左辺値参照を受け取るが、右辺値を使用して呼び出されるため factory
です。
Z* pz = factory<Z>(2, 2);
通常、この問題を解決するには、factory
および A&
パラメーターの任意の組み合わせで、const A&
関数のオーバーロードされたバージョンを作成する必要があります。 右辺値参照を使用すると、次の例に示すように、1 つのバージョンの factory
関数を作成できます。
template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}
この例では、factory
関数へのパラメーターとして、右辺値参照を使用しています。 この関数の std::forward
目的は、ファクトリ関数のパラメーターをテンプレート クラスのコンストラクターに転送することです。
次の例は、main
、factory
、W
、および X
クラスのインスタンスを作成するために、変更済みの Y
関数を使用する Z
関数を示します。 変更された factory
関数は、そのパラメーター (左辺値または右辺値) を適切なクラス コンストラクターに転送します。
int main()
{
int a = 4, b = 5;
W* pw = factory<W>(a, b);
X* px = factory<X>(2, b);
Y* py = factory<Y>(a, 2);
Z* pz = factory<Z>(2, 2);
delete pw;
delete px;
delete py;
delete pz;
}
右辺値参照のプロパティ
関数をオーバーロードして、左辺値参照と右辺値参照を取得できます。
const
の左辺値参照または右辺値参照を受け取るように関数をオーバーロードすることによって、変更できないオブジェクト (左辺値) と変更可能で一時的な値 (右辺値) を区別するコードを記述できます。 オブジェクトが const
としてマークされていない限り、右辺値参照を受け取る関数にオブジェクトを渡すことができます。 次の例は、左辺値参照と右辺値参照を受け取るためにオーバーロードされた関数 f
を示しています。 main
関数は、左辺値と右辺値の両方で f
を呼び出します。
// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
// A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
};
void f(const MemoryBlock&)
{
cout << "In f(const MemoryBlock&). This version can't modify the parameter." << endl;
}
void f(MemoryBlock&&)
{
cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}
int main()
{
MemoryBlock block;
f(block);
f(MemoryBlock());
}
この例を実行すると、次の出力が生成されます。
In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.
この例では、f
の最初の呼び出しで、引数としてローカル変数 (左辺値) を渡します。 f
の 2 番目の呼び出しでは、引数として一時オブジェクトを渡します。 一時オブジェクトはプログラムの他の場所で参照できないため、呼び出しは、rvalue 参照を受け取る、f
のオーバーロードされたバージョンにバインドされます。そのため、オブジェクトは自由に変更できます。
コンパイラは、名前付き右辺値参照を左辺値として扱い、名前のない右辺値参照を右辺値として扱います。
rvalue 参照をパラメーターとして受け取る関数では、パラメーターは関数の本体で lvalue として扱われます。 コンパイラでは、名前付きの rvalue 参照を lvalue として扱います。 これは、名前付きオブジェクトをプログラムの複数の部分から参照できるためです。 プログラムの複数の部分に、リソースの変更やそのオブジェクトからの削除を許可することは危険です。 たとえば、プログラムの複数の部分で同じオブジェクトからリソースを転送しようとした場合、最初の転送だけが成功します。
次の例は、左辺値参照と右辺値参照を受け取るためにオーバーロードされた関数 g
を示しています。 関数 f
は右辺値参照をパラメーター (名前付きの右辺値参照) として受け取り、右辺値参照 (名前のない右辺値参照) を返します。 g
からの f
の呼び出しでは、g
の本体がパラメーターを左辺値として処理するので、オーバーロードの解決は、左辺値参照を受け取る f
のバージョンを選択します。 g
からの main
の呼び出しでは、g
が右辺値参照を返すので、オーバーロードの解決は、右辺値参照を受け取る f
のバージョンを選択します。
// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
// A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
};
void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
}
void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
}
MemoryBlock&& f(MemoryBlock&& block)
{
g(block);
return move(block);
}
int main()
{
g(f(MemoryBlock()));
}
この例を実行すると、次の出力が生成されます。
In g(const MemoryBlock&).
In g(MemoryBlock&&).
この例では、main
関数から rvalue を f
に渡します。 f
の本体は、名前付きパラメーターを左辺値として処理します。 f
からの g
の呼び出しにより、パラメーターは左辺値参照 (g
の最初のオーバーロードされたバージョン) にバインドされます。
- 左辺値を右辺値参照にキャストできます。
C++ 標準ライブラリの std::move
関数を使用すると、オブジェクトを、そのオブジェクトへの rvalue 参照に変換することができます。 または、次の例に示すように、static_cast
キーワードを使用して、lvalue を rvalue 参照にキャストできます。
// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;
// A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
};
void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
}
void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
}
int main()
{
MemoryBlock block;
g(block);
g(static_cast<MemoryBlock&&>(block));
}
この例を実行すると、次の出力が生成されます。
In g(const MemoryBlock&).
In g(MemoryBlock&&).
関数テンプレートは、テンプレート引数の型を推測し、参照折りたたみ規則を使用します。
別の関数にパラメーターを渡す ("転送する") 関数テンプレートは、一般的なパターンです。 rvalue 参照を受け取る関数テンプレートに対して、テンプレート型推論がどのように機能するかを理解しておくことは重要です。
関数の引数が右辺値の場合、コンパイラは引数が右辺値参照であると推測します。 たとえば、型をパラメーターとして受け取るT&&
関数テンプレートに、型X
のオブジェクトへの右辺値参照を渡すとします。 テンプレート引数の推論では、T
は X
と推測されるため、このパラメーターの型は X&&
になります。 関数の引数が左辺値または const
左辺値の場合、コンパイラではその型を左辺値参照またはその型の const
左辺値参照であると推測します。
次の例では、1 つの構造テンプレートを宣言し、それをさまざまな参照型に特化します。 print_type_and_value
関数は、パラメーターとして右辺値参照を取り、S::print
メソッドの適切な特殊化バージョンにそれを転送します。 main
関数は S::print
メソッドを呼び出すさまざまな方法を示します。
// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;
template<typename T> struct S;
// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.
template<typename T> struct S<T&> {
static void print(T& t)
{
cout << "print<T&>: " << t << endl;
}
};
template<typename T> struct S<const T&> {
static void print(const T& t)
{
cout << "print<const T&>: " << t << endl;
}
};
template<typename T> struct S<T&&> {
static void print(T&& t)
{
cout << "print<T&&>: " << t << endl;
}
};
template<typename T> struct S<const T&&> {
static void print(const T&& t)
{
cout << "print<const T&&>: " << t << endl;
}
};
// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
S<T&&>::print(std::forward<T>(t));
}
// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }
int main()
{
// The following call resolves to:
// print_type_and_value<string&>(string& && t)
// Which collapses to:
// print_type_and_value<string&>(string& t)
string s1("first");
print_type_and_value(s1);
// The following call resolves to:
// print_type_and_value<const string&>(const string& && t)
// Which collapses to:
// print_type_and_value<const string&>(const string& t)
const string s2("second");
print_type_and_value(s2);
// The following call resolves to:
// print_type_and_value<string&&>(string&& t)
print_type_and_value(string("third"));
// The following call resolves to:
// print_type_and_value<const string&&>(const string&& t)
print_type_and_value(fourth());
}
この例を実行すると、次の出力が生成されます。
print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth
print_type_and_value
関数の各呼び出しを解決するため、コンパイラでは最初にテンプレート引数の推論を行います。 次に、コンパイラでは、推測されたテンプレート引数にパラメーター型を置き換えるときに、参照縮小規則を適用します。 たとえば、s1
関数にローカル変数 print_type_and_value
を渡すと、コンパイラで次の関数シグネチャが生成されます。
print_type_and_value<string&>(string& && t)
コンパイラでは、参照縮小規則を使用して以下のようにシグネチャを減らします。
print_type_and_value<string&>(string& t)
このバージョンの print_type_and_value
関数は、S::print
メソッドの正しい特化されたバージョンにパラメーターを転送します。
次の表は、テンプレートの引数の型を推論するときの参照縮小規則をまとめたものです。
展開された型 | 縮小された型 |
---|---|
T& & |
T& |
T& && |
T& |
T&& & |
T& |
T&& && |
T&& |
テンプレート引数の推論は、完全転送を実装するうえでの重要な要素です。 完全 転送 セクションでは、完全な転送について詳しく説明します。
まとめ
右辺値参照は、左辺値を右辺値と区別します。 アプリケーションのパフォーマンスを向上させるには、不要なメモリ割り当てとコピー操作の必要性をなくします。 また、任意の引数を受け取る関数を記述することもできます。 その関数では、他の関数が直接呼び出された場合と同様に、それらを別の関数に転送できます。
関連項目
単項演算子を含む式
Lvalue 参照宣言子: &
左辺値と右辺値
移動コンストラクターと移動代入演算子 (C++)
C++ 標準ライブラリ
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示