Reference declaration
既存のオブジェクトまたは関数へのエイリアスである名前付き参照変数を宣言します。
目次 |
構文
参照変数の宣言は、 宣言子 が以下の形式を持つ任意の単純宣言です
&
attr
(オプション)
declarator
|
(1) | ||||||||
&&
attr
(オプション)
declarator
|
(2) | (C++11以降) | |||||||
D
を
decl-specifier-seq
S
によって決定される型への
lvalue reference
として宣言する。
D
を
rvalue reference
として宣言し、その型は
decl-specifier-seq
S
によって決定される。
| declarator | - | 参照宣言子、 配列宣言子 、および ポインタ宣言子 を除く任意の 宣言子 (参照への参照、参照の配列、参照へのポインタは存在しない) |
| attr | - | (C++11以降) 属性 のリスト |
参照は有効なオブジェクトまたは関数を参照するように初期化する必要があります: reference initialization を参照してください。
「(可能性としてcv修飾された) void への参照」型を形成することはできません。
参照型はトップレベルで
cv修飾
することはできません。宣言においてそのための構文は存在せず、typedef名
または
decltype
指定子、
(C++11以降)
または
型テンプレートパラメータ
に修飾子が追加された場合、それは無視されます。
参照はオブジェクトではありません。必ずしもストレージを占有するわけではありませんが、コンパイラが必要なセマンティクスを実装するためにストレージを割り当てる場合があります(例:参照型の非静的データメンバは通常、メモリアドレスを格納するために必要な量だけクラスのサイズを増加させます)。
参照はオブジェクトではないため、参照の配列、参照へのポインタ、参照への参照は存在しません:
int& a[3]; // エラー int&* p; // エラー int& &r; // エラー
参照の畳み込みテンプレートやtypedefにおける型操作を通じて参照への参照を形成することが許可されており、その場合 参照の畳み込み 規則が適用されます:右辺値参照から右辺値参照への畳み込みは右辺値参照となり、その他の全ての組み合わせは左辺値参照を形成します: typedef int& lref; typedef int&& rref; int n; lref& r1 = n; // type of r1 is int& lref&& r2 = n; // type of r2 is int& rref& r3 = n; // type of r3 is int& rref&& r4 = 1; // type of r4 is int&&
(これは、
テンプレート引数推論
における特別な規則(関数テンプレートで
|
(C++11以降) |
Lvalue参照
Lvalue参照は既存のオブジェクトのエイリアスとして使用できます(オプションで異なるcv修飾を付加可能):
#include <iostream> #include <string> int main() { std::string s = "Ex"; std::string& r1 = s; const std::string& r2 = s; r1 += "ample"; // sを変更 // r2 += "!"; // エラー: const参照を通して変更できない std::cout << r2 << '\n'; // sを出力(現在は"Example"を保持) }
これらは関数呼び出しにおける参照渡しのセマンティクスを実装するためにも使用できます:
#include <iostream> #include <string> void double_string(std::string& s) { s += s; // 's' は main() の 'str' と同じオブジェクト } int main() { std::string str = "Test"; double_string(str); std::cout << str << '\n'; }
関数の戻り値型が左辺値参照の場合、関数呼び出し式は lvalue 式になります:
#include <iostream> #include <string> char& char_number(std::string& s, std::size_t n) { return s.at(n); // string::at() returns a reference to char } int main() { std::string str = "Test"; char_number(str, 1) = 'a'; // the function call is lvalue, can be assigned to std::cout << str << '\n'; }
Rvalue参照右辺値参照は一時オブジェクトの 寿命を延長 するために使用できます(左辺値参照からconstへの参照も一時オブジェクトの寿命を延長できますが、それらを通じて変更することはできません):
このコードを実行
#include <iostream> #include <string> int main() { std::string s1 = "Test"; // std::string&& r1 = s1; // エラー: 左辺値にバインドできない const std::string& r2 = s1 + s1; // OK: constへの左辺値参照は寿命を延長する // r2 += "Test"; // エラー: const参照を通して変更できない std::string&& r3 = s1 + s1; // OK: 右辺値参照は寿命を延長する r3 += "Test"; // OK: 非const参照を通して変更できる std::cout << r3 << '\n'; } さらに重要なことに、関数が右辺値参照と左辺値参照の両方の オーバーロード を持つ場合、右辺値参照のオーバーロードは右辺値(prvalueとxvalueの両方を含む)にバインドし、左辺値参照のオーバーロードは左辺値にバインドします:
このコードを実行
#include <iostream> #include <utility> void f(int& x) { std::cout << "lvalue reference overload f(" << x << ")\n"; } void f(const int& x) { std::cout << "lvalue reference to const overload f(" << x << ")\n"; } void f(int&& x) { std::cout << "rvalue reference overload f(" << x << ")\n"; } int main() { int i = 1; const int ci = 2; f(i); // f(int&) を呼び出す f(ci); // f(const int&) を呼び出す f(3); // f(int&&) を呼び出す // f(int&&) オーバーロードが提供されていない場合は f(const int&) を呼び出す f(std::move(i)); // f(int&&) を呼び出す // rvalue reference 変数は式内で使用されるとき lvalue となる int&& x = 1; f(x); // f(int& x) を呼び出す f(std::move(x)); // f(int&& x) を呼び出す } これにより、適切な場合に move constructors 、 move assignment 演算子、およびその他のmove対応関数(例: std::vector::push_back() )が自動的に選択されるようになります。 rvalue参照はxvalueに束縛できるため、非一時オブジェクトを参照することができます: int i2 = 42; int&& rri = std::move(i2); // i2に直接バインドする これにより、不要になったスコープ内のオブジェクトから移動することが可能になります: std::vector<int> v{1, 2, 3, 4, 5}; std::vector<int> v2(std::move(v)); // v に右辺値参照をバインドする assert(v.empty()); 転送参照 (Forwarding references)転送参照は、関数引数の値カテゴリを保持する特別な種類の参照であり、 転送 を std::forward によって可能にします。転送参照は以下のいずれかです:
1)
関数テンプレートの関数パラメータで、同じ関数テンプレートのcv修飾されていない
type template parameter
への右辺値参照として宣言されている場合:
template<class T> int f(T&& x) // xは転送参照 { return g(std::forward<T>(x)); // そのため転送可能 } int main() { int i; f(i); // 引数は左辺値、f<int&>(int&)を呼び出し、std::forward<int&>(x)は左辺値 f(0); // 引数は右辺値、f<int>(int&&)を呼び出し、std::forward<int>(x)は右辺値 } template<class T> int g(const T&& x); // xは転送参照ではない: const TはCV修飾されていない型ではない template<class T> struct A { template<class U> A(T&& x, U&& y, int* p); // xは転送参照ではない: Tはコンストラクタの // 型テンプレートパラメータではないが、 // yは転送参照である };
2)
auto
&&
波括弧で囲まれた初期化子リストから推論される場合を除く
または、クラステンプレートのテンプレートパラメータを表す場合で
クラステンプレート引数推論
中である場合
(C++17以降)
:
auto&& vec = foo(); // foo() は左辺値または右辺値の可能性があり、vec は転送参照です auto i = std::begin(vec); // どちらの場合でも動作します (*i)++; // どちらの場合でも動作します g(std::forward<decltype(vec)>(vec)); // 値カテゴリを保持して転送します for (auto&& x: f()) { // x は転送参照です。これはジェネリックコードで範囲ベースforループを使用する一般的な方法です } auto&& z = {1, 2, 3}; // 転送参照では*ありません*(初期化子リストの特別なケース) 関連項目 template argument deduction および std::forward . |
(C++11以降) |
ダングリング参照
参照は初期化時に常に有効なオブジェクトまたは関数を参照しますが、参照先オブジェクトの 寿命 が終了した後も参照がアクセス可能なままとなるプログラムを作成することは可能です( dangling )。
参照型の式 expr が与えられ、参照によって示されるオブジェクトまたは関数を target とする場合:
- target へのポインタが expr の評価コンテキストにおいて 有効 である場合、結果は target を指す。
- そうでない場合、動作は未定義である。
std::string& f() { std::string s = "Example"; return s; // sのスコープを抜ける: // デストラクタが呼び出され、ストレージが解放される } std::string& r = f(); // ダングリング参照 std::cout << r; // 未定義動作: ダングリング参照からの読み取り std::string s = f(); // 未定義動作: ダングリング参照からのコピー初期化
右辺値参照およびconstへの左辺値参照は一時オブジェクトの寿命を延長することに注意してください(規則と例外については Reference initialization を参照)。
参照先のオブジェクトが破棄された(例えば明示的なデストラクタ呼び出しによって)が、ストレージが解放されなかった場合、寿命外のオブジェクトへの参照は限定的な方法で使用でき、同じストレージでオブジェクトが再作成された場合は有効になる可能性があります(詳細は 寿命外アクセス を参照してください)。
型非アクセス可能参照
オブジェクトへの参照をバインドしようとした際、変換された初期化子が lvalue (C++11まで) glvalue (C++11以降) であり、そのオブジェクトが type-accessible でない場合、未定義動作が発生します:
char x alignas(int); int& ir = *reinterpret_cast<int*>(&x); // 未定義動作: // 初期化子がcharオブジェクトを参照
呼び出し互換性のない参照
参照を関数にバインドしようとする際、変換された初期化子が lvalue (C++11まで) glvalue (C++11以降) であり、その型が関数の定義の型と call-compatible でない場合、未定義動作が発生します:
void f(int); using F = void(float); F& ir = *reinterpret_cast<F*>(&f); // 未定義動作: // 初期化子がvoid(int)関数を参照しています
注記
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_rvalue_references
|
200610L
|
(C++11) | Rvalue references |
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用バージョン | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 453 | C++98 | 参照がどのオブジェクトや関数に束縛できないか不明確だった | 明確化された |
| CWG 1510 | C++11 | decltype のオペランドでCV修飾参照を形成できなかった | 許可された |
| CWG 2550 | C++98 | パラメータが「 void への参照」型を持つことができた | 禁止された |
| CWG 2933 | C++98 | ダングリング参照へのアクセス動作が不明確だった | 明確化された |
外部リンク
| Thomas Becker, 2013 - C++ Rvalue References Explained |
| Thomas Becker, 2013 - C++ Rvalue References Explained |