4

与えられた:

int& foo(); // don't care what the reference is to
int intVal;

次の2つのケースでは、右側は同じ関数呼び出しです

int& intRef = foo();
intVal = foo();  // a reference is returned... a value is assigned.

2番目のケースでは、返された参照はどのように値に「変換」されますか?

それはintの代入演算子によって行われますか?

4

3 に答える 3

9

言語レベルでは、「参照の逆参照」などの概念はありません。参照は、左辺値の概念を実装します。変数と参照は基本的に同じものです。変数と参照の唯一の違いは、変数はコンパイラによってストレージ内のその場所に自動的にバインドされるのに対し、参照は通常、実行時のユーザー アクションによってバインドされることです。

あなたの例では、 と の間に概念的な違いはintRefありませんintVal。どちらも type の左辺値ですint。概念レベルでは、両方が同じメカニズムを介してアクセスされます。プログラム内のすべての変数を、コンパイラによって暗黙的に事前にバインドされた参照と考えることもできます。これは基本的に、Bjarne Stroustrup が TC++PL で (逐語的ではなく) 参照を既存の変数の単なる代替名と考えることができると言ったときに意味することです。

2 つの違いがわかる唯一の瞬間は、これらのエンティティを作成して初期化するときです。参照の初期化は、それをストレージ内のある場所にバインドする行為です。変数の初期化は、初期値を既存のストレージにコピーする行為です。

しかし、参照が初期化されると、それは通常の変数として機能します。参照を読み書きする行為は、それがバインドされている記憶域の場所を読み書きする行為です。参照のアドレスを取得すると、それがバインドされている格納場所のアドレスに評価されます。等々。

多くの場合、参照が変装したポインタとして内部的に実装されていることは秘密ではありません。つまり、アクセスするたびに暗黙的に逆参照される不可視のポインタです。そのような場合 (ポインターを介して実際に実装されている場合)、アクセスするたびに逆参照が行われます。したがって、質問で尋ねるように、それを行うのは代入演算子ではありません。コード内でその参照の名前に言及したこと自体が、目に見えないポインターが逆参照される原因です。

ただし、「既存の変数の代替名」を実装するエンティティは、必ずしもそれ自体のストレージを必要としません。つまり、コンパイルされた言語では、隠しポインターなどのマテリアルで表す必要はありません。これが、言語標準が 8.3.2 で「参照がストレージを必要とするかどうかは指定されていない」と述べている理由です。

于 2012-12-20T23:15:38.310 に答える
3

fooタイプ「int」のオブジェクトへの参照を返しています。その「int」がどこから来たのかは気にせず、それが存在すると仮定します。

最初の行 は、 の戻り値によって参照されるものとまったく同じ "int" 型のオブジェクトも参照する をint& intRef = foo()作成します。intReffoo

2 行目の value ofintValは、返された参照によって参照されるオブジェクトの値に置き換えられます。


あなたのコメントに応えて:

ポインターと参照の間で非常に混乱しているようです。参照は、オブジェクトの別名のようなものです。参照に対して何かを行うと、参照先のオブジェクトに実際に影響します。

参照を逆参照するようなことはありません。逆参照できるのはポインターのみです。逆参照とは、単項演算子を使用し*て、ポイントが指すオブジェクトを取得する行為です。たとえば、 がある場合、それが指しているオブジェクトを取得するint* pことができます。*pこれは逆参照 pです。

参照に対して実行できるのは*、参照先のオブジェクトがポインターである場合 (またはオーバーロードされている場合operator*) だけです。あなたの場合、をfoo返すのでint&、それを逆参照することはできません。式*foo()はコンパイルされません。これは、 の戻り値のfoo型が「int」であり、ポインターではなく、オーバーロードされないためoperator*です。

fooすべての意図と目的のために、返された参照を単にそれが参照するオブジェクトとして扱うことができます。この値をに代入することは、次のコードでに代入するintValことと実際には違いはありません。xintVal

int intVal;
int x = 5;
intVal = x;

お分かりだと思いますが、 にintValは の値が与えられますx。これは、標準によって簡単に定義されます。

単純代入 ( =) では、式の値が左オペランドによって参照されるオブジェクトの値に置き換わります。

演算子の両側が同じ型であるため、変換を行う必要はまったくありません。

これは実際にあなたの状況と同じです。あなたはただ持っています:

intVal = some_ref_to_int;

some_ref_to_int式はどこにありますかfoo()。それが参考になるという事実は問題ではありません。intVal参照が示すオブジェクトの値を受け取ります。

于 2012-12-20T23:25:40.767 に答える
1

代入は、標準の5.17[exp.ass]で定義されてintValいる代入式です。代入式の文法規則は、他のいくつかの文法規則に応じて非常に複雑ですが、基本的には、演算子の左側に変更可能な左辺値=、右側にprvalue式が必要です。

の場合

intVal = foo();

RHSの式は型の左辺値であるintため、組み込みの左辺値から右辺値への変換が行われます...これは、値が変更されず、型も変更されないという点で、ほとんど変換ではありません(基本型のcv-qualifiersは削除されるため、左辺値がtypeの場合const int、prvalueはtypeになりますint)。[conv.lval]は言います

非関数、非配列型のglvalue(3.10)は、Tprvalueに変換できます。[...]Tが非クラス型の場合、prvalueの型はcv-unqualifiedバージョンのですT。それ以外の場合、prvalueのタイプはですT。[...] glvalueで示されるオブジェクトに含まれる値は、prvalueの結果です。

したがって、prvalueの型intfoo()値は、返された参照がバインドされている変数と同じ値になります。

代入式の規則は次のように述べています。

単純な代入(=)では、式の値は、左のオペランドによって参照されるオブジェクトの値を置き換えます。

したがって、の値intValはprvalueの値に置き換えられます。ルールは続きます:

左のオペランドがクラス型でない場合、式は暗黙的に左のオペランドのcv非修飾型に変換されます(第4節)。

したがって、intはクラス型ではないため(したがって、オーバーロードされていないためoperator=、組み込みの代入演算子を使用するだけです)、代入はRHSをに変換intします。これは、既に使用している型です。

したがって、の値はintValprvalueの値に設定されます。これは、glvalue式foo()の値、つまり、参照がバインドされている変数の値です。

左辺値から右辺値への変換は、RHSが参照であることとは関係がないことに注意してください。ここでも同じことが起こります。

int val = 0;
intVal = val;

valは型の左辺値であるintため、型のprvalueに変換され、の値はそのprvalueのintintValに設定されます。

ルールは、参照であるかどうかではなく、式の「値のカテゴリ」(つまり、左辺値または右辺値)で表されます。必要な参照の「逆参照」は、必要な動作を実装するために、コンパイラによって暗黙的かつ非表示で行われます。

于 2012-12-21T00:30:38.240 に答える