連休中の暇つぶしで「値渡しと参照渡しの違いについて、シンプルにかつ首尾一貫して説明する方法はないか」を考えていました。そこで自分なりにこんな説明を考えてみました、という話です。
なお、文中で「参照」という言葉を使っていますが、ぼくはC言語しか知らない(のと、C++は最近勉強はじめた)ため、「参照」という言葉は他の言語ではまた別の意味を持つかもしれません。
##値渡しについて
「Cには値渡ししか存在しない」は周知の事実ですが、例えば以下のコードにおいて、関数呼び出しの第一引数にm(==10)
を指定した場合、関数f()
には10
というスカラー値を渡します。
また、第二引数にポインタ&n
を渡した場合、式&n
を評価した結果得られるポインタ(スカラー値)を関数f()
に渡します。
#include <stdio.h>
void f(int m_, int *n_)
{
m_++;
(*n_)++;
}
int main(int argc, char **argv)
{
int m=1;
int n=1;
f(m, &n);
printf("m=%d n=%d\n", m, n); /* m=1 n=2 */
return 0;
}
上記コードでは、関数に渡されたポインタ(n_
)を関数内部で間接参照して被参照オブジェクト(n
)にアクセスしていますが、それは渡されたオブジェクトを関数内部でどのように使うかの「用途の話」であって、引数の渡し方とは関係ないです。整数値だろうがポインタ値だろうが、オブジェクトを渡していることには変わりは無いです。
##別名を使う
関数にポインタを渡す意図として、データコピーのオーバーヘッドを抑止したい理由もありますが、変数スコープの制約上、呼び出し先の関数内からは呼び出し元で宣言された実引数のオブジェクトにアクセスできないので、その制約を回避したいためもあると思います(バイナリハックとかはナシで)。
そこでC++の参照型の登場ですが、呼び出し元の実引数のオブジェクト(n
)に関数内でローカルスコープを持つ別名n_
を紐付けることで、呼び出し先の関数内では「n_
」というシンボル名を使ってオブジェクトn
を(あたかもn
を扱うかのごとく)ハンドリングすることができます。
#include <iostream>
void f(int m_, int &n_)
{
m_++;
n_++;
}
int main(int argc, char **argv)
{
int m=1;
int n=1;
f(m, n);
std::cout << "m=" << m << " n=" << n << std::endl;
// m=1 n=2
return 0;
}
上記のような引数渡しについて、本来なら「参照渡し」と言うべきなのでしょうが、「参照」というとポインタの間接参照とごっちゃになってしまうため、「参照」という言葉の使用はなるべく避けたい(ポインタ説明のためにとっておきたい)のです。
そこで僕は個人的に「オブジェクトの別名束縛」と呼んでいます。対して、俗に言う「値渡し」は「オブジェクトの値渡し」と呼んでいます(ポインタの値渡しはこれの一種)。
ただ、上記n
とn_
のように対応関係にある実引数と仮引数はそもそもスコープが異なるのだから同一シンボル名を付与できるとか、あるいは配列の部分配列、定数、配列型配列の配列要素など名前の無いオブジェクトに名前をつけることも可能なので、「別名」というのもまたビミョーに名が体を表してないのですが…難しいですね。