名前空間
変種
操作

参照宣言

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

名前付き変数を参照として宣言します。これは、既存のオブジェクトまたは関数のエイリアスとして機能します。

目次

[編集] 構文

参照変数の宣言は、宣言子が次の形式を持つ単純な宣言です。

& 属性 (オプション) 宣言子 (1)
&& 属性 (オプション) 宣言子 (2) (C++11以降)
1) 左辺値参照宣言子: 宣言 S& D; は、Ddecl-specifier-seq S によって決定される型への左辺値参照として宣言します。
2) 右辺値参照宣言子: 宣言 S&& D; は、Ddecl-specifier-seq S によって決定される型への右辺値参照として宣言します。
declarator (宣言子) - 他の参照宣言子を除く任意の宣言子(参照への参照は存在しない)
attr - (C++11以降) 属性のリスト

参照は、有効なオブジェクトまたは関数を参照するために初期化する必要があります。詳細については、参照初期化を参照してください。

「(おそらくcv修飾された)voidへの参照」という型は作成できません。

参照型は、トップレベルでcv修飾できません。宣言にはそのための構文がなく、typedef名またはdecltype指定子、(C++11以降)または型テンプレートパラメータに修飾が追加されても、それは無視されます。

参照はオブジェクトではありません。必ずしもストレージを占有するわけではありませんが、コンパイラは目的のセマンティクスを実装するために必要であればストレージを割り当てる場合があります(例:参照型の非静的データメンバは通常、メモリアドレスを格納するために必要な量だけクラスのサイズを増加させます)。

参照はオブジェクトではないため、参照の配列、参照へのポインタ、参照への参照はありません。

int& a[3]; // error
int&* p;   // error
int& &r;   // error

参照の畳み込み (Reference collapsing)

テンプレートやtypedefsの型操作を介して参照への参照を形成することは許可されており、その場合、参照の畳み込み規則が適用されます。右辺値参照への右辺値参照は右辺値参照に畳み込まれ、他のすべての組み合わせは左辺値参照を形成します。

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&&

(これは、関数テンプレートでT&&が使用される場合のテンプレート引数推論の特殊な規則と合わせて、std::forwardを可能にする規則を形成します。)

(C++11以降)

[編集] 左辺値参照

左辺値参照は、既存のオブジェクトのエイリアスとして使用できます(オプションで異なるcv修飾子を使用できます)。

#include <iostream>
#include <string>
 
int main()
{
    std::string s = "Ex";
    std::string& r1 = s;
    const std::string& r2 = s;
 
    r1 += "ample";           // modifies s
//  r2 += "!";               // error: cannot modify through reference to const
    std::cout << r2 << '\n'; // prints s, which now holds "Example"
}

これらは、関数呼び出しで参照渡しセマンティクスを実装するためにも使用できます。

#include <iostream>
#include <string>
 
void double_string(std::string& s)
{
    s += s; // 's' is the same object as main()'s '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() 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';
}

右辺値参照

右辺値参照は、一時オブジェクトの寿命を延長するために使用できます(定数への左辺値参照も一時オブジェクトの寿命を延長できますが、それらを通して変更することはできません)。

#include <iostream>
#include <string>
 
int main()
{
    std::string s1 = "Test";
//  std::string&& r1 = s1;           // error: can't bind to lvalue
 
    const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
//  r2 += "Test";                    // error: can't modify through reference to const
 
    std::string&& r3 = s1 + s1;      // okay: rvalue reference extends lifetime
    r3 += "Test";                    // okay: can modify through reference to non-const
    std::cout << r3 << '\n';
}

さらに重要なのは、関数が右辺値参照と左辺値参照の両方のオーバーロードを持つ場合、右辺値参照オーバーロードは右辺値(prvaluesとxvaluesの両方を含む)にバインドし、左辺値参照オーバーロードは左辺値にバインドする点です。

#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);  // calls f(int&)
    f(ci); // calls f(const int&)
    f(3);  // calls f(int&&)
           // would call f(const int&) if f(int&&) overload wasn't provided
    f(std::move(i)); // calls f(int&&)
 
    // rvalue reference variables are lvalues when used in expressions
    int&& x = 1;
    f(x);            // calls f(int& x)
    f(std::move(x)); // calls f(int&& x)
}

これにより、ムーブコンストラクタムーブ代入演算子、およびその他のムーブ対応関数(例:std::vector::push_back())を、適切な場合に自動的に選択できるようになります。

右辺値参照はxvaluesにバインドできるため、一時的ではないオブジェクトを参照できます。

int i2 = 42;
int&& rri = std::move(i2); // binds directly to i2

これにより、もはや不要なスコープ内のオブジェクトからムーブアウトすることが可能になります。

std::vector<int> v{1, 2, 3, 4, 5};
std::vector<int> v2(std::move(v)); // binds an rvalue reference to v
assert(v.empty());

転送参照 (Forwarding references)

転送参照は、関数引数の値カテゴリを保持する特殊な種類の参照であり、std::forwardによってそれを転送することを可能にします。転送参照は次のいずれかです。

1) その同じ関数テンプレートのcv修飾されていない型テンプレートパラメータへの右辺値参照として宣言された関数テンプレートの関数パラメータ
template<class T>
int f(T&& x)                      // x is a forwarding reference
{
    return g(std::forward<T>(x)); // and so can be forwarded
}
 
int main()
{
    int i;
    f(i); // argument is lvalue, calls f<int&>(int&), std::forward<int&>(x) is lvalue
    f(0); // argument is rvalue, calls f<int>(int&&), std::forward<int>(x) is rvalue
}
 
template<class T>
int g(const T&& x); // x is not a forwarding reference: const T is not cv-unqualified
 
template<class T>
struct A
{
    template<class U>
    A(T&& x, U&& y, int* p); // x is not a forwarding reference: T is not a
                             // type template parameter of the constructor,
                             // but y is a forwarding reference
};
2) auto&& (ただし、波括弧で囲まれた初期化子リストから推論される場合を除く、またはクラステンプレート引数推論中にクラステンプレートのテンプレートパラメータを表す場合を除く(C++17以降)
auto&& vec = foo();       // foo() may be lvalue or rvalue, vec is a forwarding reference
auto i = std::begin(vec); // works either way
(*i)++;                   // works either way
 
g(std::forward<decltype(vec)>(vec)); // forwards, preserving value category
 
for (auto&& x: f())
{
    // x is a forwarding reference; this is a common way to use range for in generic code
}
 
auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists)

詳細については、テンプレート引数推論およびstd::forwardも参照してください。

(C++11以降)

[編集] ダングリング参照

参照は初期化時に常に有効なオブジェクトまたは関数を参照しますが、参照先のオブジェクトの寿命が終了しても参照がアクセス可能なままになるプログラムを作成することは可能です(ダングリング)。

参照型の式exprが与えられ、targetが参照によって示されるオブジェクトまたは関数であるとします。

  • targetへのポインタがexprの評価のコンテキストで有効である場合、結果はtargetを指します。
  • それ以外の場合、動作は未定義です。
std::string& f()
{
    std::string s = "Example";
    return s; // exits the scope of s:
              // its destructor is called and its storage deallocated
}
 
std::string& r = f(); // dangling reference
std::cout << r;       // undefined behavior: reads from a dangling reference
std::string s = f();  // undefined behavior: copy-initializes from a dangling reference

右辺値参照とconstへの左辺値参照は一時オブジェクトの寿命を延長することに注意してください(規則と例外については参照初期化を参照)。

参照先のオブジェクトが破棄された(例:明示的なデストラクタ呼び出しによって)が、ストレージが解放されていない場合、寿命が終了したオブジェクトへの参照は限定的な方法で使用でき、同じストレージにオブジェクトが再作成された場合は有効になる可能性があります(詳細については寿命外アクセスを参照)。

[編集] 型にアクセスできない参照 (Type-inaccessible references)

変換された初期化子が、オブジェクトが型にアクセスできない左辺値(C++11まで)glvalue(C++11以降)であるオブジェクトに参照をバインドしようとすると、未定義の動作になります。

char x alignas(int);
 
int& ir = *reinterpret_cast<int*>(&x); // undefined behavior:
                                       // initializer refers to char object

[編集] 呼び出しと互換性のない参照 (Call-incompatible references)

変換された初期化子が、関数の定義の型と呼び出しと互換性がない左辺値(C++11まで)glvalue(C++11以降)である関数に参照をバインドしようとすると、未定義の動作になります。

void f(int);
 
using F = void(float);
F& ir = *reinterpret_cast<F*>(&f); // undefined behavior:
                                   // initializer refers to void(int) function

[編集] 備考

機能テストマクロ 規格 機能
__cpp_rvalue_references 200610L (C++11) 右辺値参照

[編集] 欠陥レポート

以下の動作変更を伴う欠陥報告が、以前に公開された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
English 日本語 中文(简体) 中文(繁體)