C++プログラミング言語において、参照は、 Cから継承されたポインタ型よりも機能は劣るものの、より安全な単純な参照データ型です。コンピュータサイエンスでは参照は一般的な概念データ型であり、ポインタとC++ 参照は特定の参照データ型の実装であるため、C ++ の参照という名称は混乱を招く可能性があります。C++ における参照の定義は、参照が存在する必要がないというものです。既存のオブジェクトに新しい名前を付けることで実装できます (Ada の rename キーワードに似ています)。
フォームの宣言:
T & r ;ここでT、は型であり、rは識別子です。は、型がへの左辺値参照であるT識別子を定義します。[ 1 ]
例:
int a = 5 ; int & refA = a ;extern int & refB ;ここで、refAおよびは「への左辺値参照」refBの型です。int
int & foo ();fooint「lvalue参照」を返す関数です。
void bar ( int & p );barは参照パラメータを持つ関数でありp、それは「 への左辺値参照int」です。
class MyClass { int & b ; };MyClassclassメンバーが左辺値参照であるint
int fn () { return 42 ; }int ( & fnRef )() = fn ; int ( && fnRef2 )() = fn ; // 基本的に上記と同等fnは (非参照型) を返す関数でありint、のエイリアスfnRefです。fn
const int & ref = 65 ;const int& refconst intこれは、値が65であるストレージの一部を指す左辺値参照です。
int a [ 3 ]; int ( & alvr )[ 3 ] = arr ; int ( && arvr )[ 3 ] = std :: move ( a ); using Triplet = int [ 3 ]; int ( && aprvl )[ 3 ] = Triplet {}; // Triplet{} は配列 prvalue ですint * const & pclv = a ; // int* const& pclv = &a[0]; と同じですint *&& prv = a ; // int*& alv = a; // エラー: 非 const 型の lvalue 参照を rvalue で初期化していますalvrは配列への参照です。配列への参照を初期化する場合、配列からポインタへの変換は行われませんが、ポインタへの参照を初期化する場合は行われます。配列からポインタへの変換はprvalueを返すため、constその結果で初期化できるのはlvalue参照とrvalue参照のみです。同様に、関数への参照を初期化する場合、関数からポインタへの変換は行われません(上記参照)が、関数ポインタfnRefへの参照を初期化する場合は行われます。
int fn () { return 42 ; }int ( * const & pFnRef )() = fn ; // int (*const &pFnRef)() = &fn; と同じint ( *&& pFnRef2 )() = fn ;フォームの宣言:
T && r ;ここでTは型であり、は識別子です。 はへの右辺値参照でrある識別子を定義します。右辺値参照の名前自体が左辺値であるため、右辺値参照パラメータを受け入れる関数オーバーロードに右辺値参照を渡すには を使用する必要があります。同じ関数テンプレートの cv 修飾されていない型テンプレートパラメータへの右辺値参照は、中括弧で囲まれた初期化リストから推論される場合を除き、転送参照(一部の古い資料[ 2 ]では「ユニバーサル参照」と呼ばれています) と呼ばれ、渡される内容に応じて左辺値参照または右辺値参照として機能します。[ 3 ] 関数パラメータで見つかった場合、呼び出し元の関数に渡されたときの値のカテゴリ (左辺値または右辺値) を保持しながら、関数引数を別の関数に転送するために と一緒に使用されることがあります。[ 4 ]Tstd::moveauto&&std::forward()
T「参照」型の型は、参照型と呼ばれることがあります。参照型の識別子は、参照変数と呼ばれます。しかし、後述するように、それらを変数と呼ぶのは実際には誤称です。
参照はオブジェクトではなく、参照はオブジェクト型または関数型のみを参照できます。参照の配列、参照へのポインタ、および参照への参照は、オブジェクト型を必要とするため許可されていません。、および はコンパイルエラーを引き起こします(ただし、(配列の参照)および(ポインタの参照)は、初期化されていると仮定するとエラーになりません)。 への参照も、 はオブジェクト型または関数型ではないため不正な形式ですが、 への参照は存在できます。int&i[4]int&*iint&&iint(&i)[4]int*&ivoidvoidvoid*
参照をconstまたはvolatile( ) として宣言することも、/が使用されていない限り失敗します。 / が使用されている場合は、 /は無視されます。ただし、テンプレート引数の推論が行われ、参照型が推論される場合 (これは、転送参照が使用され、lvalue が関数に渡される場合に発生します)、または、または が参照型を表す場合は、その型への参照を取得できます。この場合、参照の型を決定するために使用されるルールは参照の縮約と呼ばれ、次のように機能します。 型と への参照型を仮定すると、 への rvalue 参照を作成しようとすると が作成され、 へのlvalue 参照はへの lvalue 参照を作成します。言い換えると、lvalue 参照は rvalue 参照を上書きし、rvalue 参照の rvalue 参照は変更されません。volatileint&itypedefdecltypeconstvolatiletypedefusingdecltypeTTTRTRTRTRT
using LrefInt = int & ; using RrefInt = int && ;int i = 0 ;LrefInt & r1 = i ; // r1 は int& 型ですconst LrefInt & r2 = i ; // r2 は int& 型ですconst LrefInt && r3 = i ; // r3 は int& 型ですRrefInt & r4 = i ; // r4 は int& 型ですRrefInt && r5 = 5 ; // r5 は int&& 型ですdecltype ( r2 ) & r6 = i ; // r6 は int& 型ですdecltype ( r2 ) && r7 = i ; // r7 は int& 型です非静的メンバ関数は、 ref 修飾子を使用して宣言できます。この修飾子はオーバーロード解決constに関与し、やのような暗黙のオブジェクトパラメータに適用されますvolatileが、これら 2 つとは異なり、のプロパティは変更しませんthis。この修飾子が行うのは、関数をクラスの lvalue または rvalue インスタンスで呼び出すことを義務付けることです。
import std ;struct A { A () = default ;void display () const & { std :: println ( "lvalue" ); }void display () const && { std :: println ( "rvalue" ); } };int main () { A a ; a.display (); // "lvalue" と表示されるstd :: move ( a ) .display ( ); // "rvalue" と表示されるA ( ). display (); // "rvalue" と表示されるA && b = std :: move ( a ); b.display ( ); // "lvalue"(!) と表示される}C++の参照は、ポインタとはいくつかの重要な点で異なります。
特に、ローカル変数とグローバル変数は定義された場所で初期化する必要があり、クラスインスタンスのデータメンバである参照は、クラスのコンストラクタの初期化リストで初期化する必要があります。例:
int & k ; // コンパイラは「エラー: `k' は参照として宣言されていますが、初期化されていません」と警告しますポインタと参照の間には単純な変換があります。アドレス演算子 ( &) は、参照に適用された場合、同じオブジェクトを参照するポインタを生成します。また、ポインタ値の逆参照 ( ) から初期化された参照は、未定義の動作*を引き起こさずにこれが可能な場合、そのポインタと同じオブジェクトを参照します。この等価性は、参照をポインタにコンパイルし、使用するたびに暗黙的に逆参照するという、一般的な実装を反映しています。通常はそうなりますが、C++ 標準では、コンパイラにポインタを使用して参照を実装することを強制していません。
その結果、多くの実装において、自動ライフタイムまたは静的ライフタイムを持つ変数に対して参照を介して操作を行う場合、構文的には直接アクセスする場合と似ているものの、コストのかかる隠れた逆参照操作が発生する可能性がある。
また、参照に対する操作は非常に限られているため、ポインタよりもはるかに理解しやすく、エラーにも強い。ポインタは、ヌル値を保持したり、範囲外の演算を行ったり、不正な型変換を行ったり、任意の整数からポインタを生成したりするなど、さまざまなメカニズムによって無効になる可能性があるが、以前は有効だった参照が無効になるのは、次の2つの場合のみである。
参照が静的スコープであれば、1つ目の問題は自動的に容易に検出できますが、参照が動的に割り当てられたオブジェクトのメンバーである場合は依然として問題となります。2つ目の問題は検出がより困難です。参照に関する懸念事項はこれらのみであり、適切な割り当てポリシーによって適切に対処できます。
参照の最も便利な使い方は、関数の引数に使うことです。参照を使うと、引数(実引数)の値を、引数を使うたびに逆参照演算子を使うことなく読み取ったり変更したりできます*。例えば、次のようになります。
void square ( int x , int & out ) { out = x * x ; }すると、次の呼び出しで 9 が次の場所に配置されますy。
int y ; square ( 3 , y );しかし、次の呼び出しではコンパイラエラーが発生します。なぜなら、で修飾されていない左辺値参照パラメータは、constアドレス指定可能な値にのみバインドできるからです。
正方形(3、6 )int & preInc ( int & x ) { return ++ x ; // "return x++;" は誤りだったでしょう}preInc ( y ) = 5 ; // ++yと同じ、y = 5constこのオーバーヘッドを回避しながら、関数間で大きなオブジェクトを渡すことができます。void slow ( BigObject x ) { // ... }void fast ( const BigObject & x ) { // ... }BigObject y ;slow ( y ); // 低速、y をパラメータ x にコピーします。fast ( y ); // 高速、y への直接読み取り専用アクセスを提供します。実際に変更可能なxfFastの独自のコピーが必要な場合は、明示的にコピーを作成する必要があります。ポインタを使用して同じ手法を適用することもできますが、そのためには関数のすべての呼び出し箇所を変更して、引数に面倒なアドレス演算子 ( ) を追加する必要があり、後でオブジェクトが小さくなった場合に元に戻すのも同様に困難になります。&
参照とポインタの関係(C++の文脈において)についてさらに述べると、前者は予想通り多態性を示す。
import std ;class A { public : A () = default ;virtual void display () { std :: println ( "これはクラス A です" ); } };class B : public A { public : B () = default ;virtual void display () { std :: println ( "これはクラス B です" ); } };int main () { A a ; A & refA = a ;B b ; A & refB = b ;refA.display ( ) ; refB.display ( ) ; }上記のソースコードは有効なC++であり、以下の出力を生成します。
これはクラスAですこれはクラスBです{{cite book}}: CS1 maint: 複数名: 著者リスト (リンク) CS1 maint: 数値名: 著者リスト (リンク)