web-dev-qa-db-ja.com

CとC ++の「参照渡し」の違いは何ですか?

「参照渡し」というフレーズは、CとC++の開発者が同じように使用していますが、異なる意味で使用されているようです。各言語におけるこのあいまいなフレーズの違いは何ですか?

26

参照渡しと値渡しの違い をすでに扱っている質問があります。本質的に、値によって引数を関数に渡すことは、関数が引数の独自のコピーを持つことを意味します-そのvalueがコピーされます。そのコピーを変更しても、元のオブジェクトは変更されません。ただし、参照渡しの場合、関数内のパラメーターは、渡されたのと同じオブジェクトを参照します。関数内の変更はすべて外部で確認されます。

あいにく、 "値渡し"と "参照渡し"という語句を使用する場合、混乱を招く2つの方法があります。これは、特にCのバックグラウンドから来た場合に、新しいC++プログラマーがポインターと参照を採用するのが難しい理由の1つだと思います。

C

Cでは、技術的な意味ですべてが値で渡されます。つまり、関数の引数として何を指定しても、その関数にコピーされます。たとえば、void foo(int)で関数foo(x)を呼び出すと、xの値がfooのパラメーターとしてコピーされます。これは簡単な例で見ることができます:

_void foo(int param) { param++; }

int main()
{
  int x = 5;
  foo(x);
  printf("%d\n",x); // x == 5
}
_

xの値がfooにコピーされ、そのコピーが増分されます。 xmainは引き続き元の値を保持します。

ご存知のとおり、オブジェクトはポインター型にすることができます。たとえば、_int* p_はpintへのポインターとして定義します。次のコードでは2つのオブジェクトが導入されていることに注意してください。

_int x = 5;
int* p = &x;
_

1つはint型で、値は_5_です。 2番目の型は_int*_で、その値は最初のオブジェクトのアドレスです。

関数へのポインターを渡すとき、値によってそれを渡します。そこに含まれるアドレスが関数にコピーされます。関数内のpointerを変更しても、関数外のポインターは変更されません-ただし、ポイントするオブジェクトを変更しますは、関数外のオブジェクトを変更します。しかし、なぜ?

同じ値を持つ2つのポインターは常に同じオブジェクト(同じアドレスを含む)を指すため、指し示されているオブジェクトは、両方からアクセスおよび変更できます。これにより、参照先のオブジェクトを参照で渡したというセマンティクスが得られますが、実際には参照が存在していません。Cには参照がありません。変更された例を見てください。

_void foo(int* param) { (*param)++; }

int main()
{
  int x = 5;
  foo(&x);
  printf("%d\n",x); // x == 6
}
_

_int*_を関数に渡すと、それが指すintは「参照渡し」されたと言えますが、実際にはintが実際にどこにも渡されず、ポインターのみが関数にコピーされました。これは私たちに口語を与える1 「値渡し」と「参照渡し」の意味。

この用語の使用法は、規格内の用語によって裏付けられています。ポインター型がある場合、それが指している型は、その参照型と呼ばれます。つまり、参照される_int*_の型はintです。

ポインタ型は、参照されていると呼ばれる関数型、オブジェクト型、または不完全型から派生する可能性がありますタイプ

単項の_*_演算子(_*p_など)は、標準では間接指定と呼ばれていますが、一般にポインタの逆参照とも呼ばれています。これにより、Cでの「参照渡し」の概念がさらに促進されます。

C++

C++は、Cの元の言語機能の多くを採用しました。その中にはポインターがあるため、この「参照渡し」の口語形式は引き続き使用できます。_*p_は、pを逆参照しています。ただし、C++には、Cにはない機能(真に参照を渡す機能)が導入されているため、この用語を使用すると混乱を招きます。

アンパサンドが後に続く型は参照型2。たとえば、_int&_はintへの参照です。参照型を取る関数に引数を渡すと、オブジェクトは参照によって本当に渡されます。関連するポインタも、オブジェクトのコピーも、何もありません。関数内の名前は、実際には渡されたオブジェクトとまったく同じものを指します。上記の例とは対照的です。

_void foo(int& param) { param++; }

int main()
{
  int x = 5;
  foo(x);
  std::cout << x << std::endl; // x == 6
}
_

これで、foo関数には、intへの参照であるパラメーターがあります。 xを渡すと、paramはまったく同じオブジェクトを参照します。 paramをインクリメントすると、xの値に目に見える変化があり、xの値は6になりました。

この例では、値によって何も渡されませんでした。何もコピーされませんでした。参照渡しは実際にはポインタを値で渡すだけだったCとは異なり、C++では純粋に参照渡しができます。

「参照渡し」という用語にはこの潜在的なあいまいさがあるため、参照型を使用する場合は、C++のコンテキストでのみ使用することをお勧めします。ポインタを渡す場合は、参照渡しではなく、値渡しのポインタを渡します(つまり、ポインタへの参照を渡さない限り、もちろん_int*&_など)。ただし、ポインタが使用されているときに「参照渡し」の使用に遭遇する可能性がありますが、これで少なくとも実際に何が起こっているかを理解できます。


他の言語

他のプログラミング言語は事態をさらに複雑にします。 Javaなどの一部の変数は、オブジェクトへの参照(C++での参照とは異なり、ポインタのようなもの)として知られていますが、それらの参照は値によって渡されます。したがって、参照によって関数に渡されているように見えても、実際に行っているのは、値によって関数に参照をコピーすることです。渡された参照に新しいオブジェクトを割り当てると、C++での参照による受け渡しとのこのわずかな違いがわかります。

_public void foo(Bar param) {
  param.something();
  param = new Bar();
}
_

Javaでこの関数を呼び出して、Bar型のオブジェクトを渡した場合、param.something()の呼び出しは、渡された同じオブジェクトで呼び出されます。これは、オブジェクトへの参照を渡したためです。ただし、新しいBarparamに割り当てられていても、関数外のオブジェクトは同じ古いオブジェクトです。新しいものは外から決して見られない。これは、foo内の参照が新しいオブジェクトに再割り当てされているためです。このような参照の再割り当ては、C++参照では不可能です。


1 「口語」によって、「参照渡し」のCの意味がC++の意味よりも真実ではないことを示唆するつもりはありません。C++には実際に参照型があるので、本当に参照。 Cの意味は、実際に値で渡されるものを抽象化したものです。

2 もちろん、これらは左辺値参照であり、C++ 11にも右辺値参照があります。

54