参照宣言
参照、つまり、既存のオブジェクトまたは関数へのエイリアスとして、名前付き変数を宣言します。
構文
参照変数宣言は宣言子が以下の形式を持つ単純宣言です。
& attr(オプション) declarator
|
(1) | ||||||||
&& attr(オプション) declarator
|
(2) | (C++11以上) | |||||||
S& D; は decl-specifier-seq S によって決定される型への左辺値参照として D を宣言します。S&& D; は decl-specifier-seq S によって決定される型への右辺値参照として D を宣言します。| declarator | - | 別の参照宣言子を除く任意の宣言子 (参照への参照はありません)。 |
| attr(C++11) | - | オプショナルな属性のリスト。 |
参照は有効なオブジェクトまたは関数への参照に初期化されることが要求されます。 参照初期化を参照してください。
void への参照および参照への参照はありません。
参照型はトップレベルで cv 修飾できません。 宣言ではそのための構文がなく、修飾が typedef、 decltype、またはテンプレート型引数を通して導入された場合、それは無視されます。
参照はオブジェクトではありません。 参照は記憶域を占めるとは限りません。 しかしコンパイラは求められる意味論を実装するために必要であれば記憶域を割り当てることがあります (例えば、参照型の非静的データメンバは、通常、メモリアドレスを格納するために必要な量だけクラスのサイズを増加させます)。
参照はオブジェクトではないため、参照の配列、参照へのポインタ、および参照への参照はありません。
int& a[3]; // エラー。
int&* p; // エラー。
int& &r; // エラー。
参照の折り畳みテンプレートまたは typedef で型操作を通して参照への参照を形成することが許されます。 この場合、参照の折り畳みのルールが適用されます。 右辺値参照への右辺値参照は右辺値参照に折り畳まれ、それ以外のすべての組み合わせは左辺値参照を形成します。 typedef int& lref;
typedef int&& rref;
int n;
lref& r1 = n; // r1 の型は int& です。
lref&& r2 = n; // r2 の型は int& です。
rref& r3 = n; // r3 の型は int& です。
rref&& r4 = 1; // r4 の型は int&& です。
(これは、関数テンプレートで |
(C++11以上) |
左辺値参照
左辺値参照は既存のオブジェクトをエイリアスするために使用できます (異なる 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';
}
関数の戻り値の型が左辺値参照のとき、関数呼び出し式は左辺値式になります。
#include <iostream>
#include <string>
char& char_number(std::string& s, std::size_t n) {
return s.at(n); // string::at() は char への参照を返します。
}
int main() {
std::string str = "Test";
char_number(str, 1) = 'a'; // この関数呼び出しは左辺値であり、代入できます。
std::cout << str << '\n';
}
右辺値参照右辺値参照は一時オブジェクトの生存期間を延長するために使用できます (const への左辺値参照も一時オブジェクトの生存期間を延長できますが、それを通して変更することはできません)。 Run this code #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 の両方を含みます) に束縛し、左辺値参照のオーバーロードは左辺値に束縛することです。 Run this code #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&&) を呼びます。
// 右辺値参照変数は式で使用されるときは左辺値です。
int&& x = 1;
f(x); // f(int& x) を呼びます。
f(std::move(x)); // f(int&& x) を呼びます。
}
これはムーブコンストラクタ、ムーブ代入演算子、およびその他のムーブ対応関数 (std::vector::push_back() など) が適切なときに自動的に選択されることを可能とします。 右辺値参照は 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());
|
(C++11以上) |
転送参照転送参照は関数の実引数の値カテゴリを維持する特別な種類の参照であり、 std::forward の意味でそれを転送することを可能とします。 転送参照は以下のいずれかです。 1) その関数テンプレートの型テンプレート引数への cv 修飾されていない右辺値参照として宣言された関数テンプレートの関数引数。
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&&。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}; // 転送参照ではありません (初期化子リストに対する特別なケースです)。
テンプレートの実引数推定および std::forward も参照してください。 |
(C++11以上) |
ダングリング参照
参照は、いったん初期化されれば、必ず有効なオブジェクトまたは関数を参照しますが、参照先のオブジェクトの生存期間は終了するけれども参照はアクセス可能な状態で残される (ダングリング) ようなプログラムを作ることは可能です。 そのような参照へのアクセスは未定義動作です。 よくある例は自動変数への参照を返す関数です。
std::string& f()
{
std::string s = "Example";
return s; // s のスコープを終了します。
// デストラクタが呼ばれ、記憶域が解放されます。
}
std::string& r = f(); // ダングリング参照。
std::cout << r; // 未定義動作、ダングリング参照からの読み込み。
std::string s = f(); // 未定義動作、ダングリング参照からのコピー初期化。
右辺値参照および const への左辺値参照は一時オブジェクトの生存期間を延長することに注意してください (ルールおよび例外については参照初期化を参照してください)。
参照先のオブジェクトは (例えば明示的なデストラクタ呼び出しによって) 破棄されたけれどもその記憶域は破棄されなかった場合、生存期間外のオブジェクトへの参照は限られた方法でのみ使用でき、そのオブジェクトが同じ記憶域に再作成されれば、有効にできます (詳細については生存期間外のアクセスを参照してください)。