らんだむな記憶

blogというものを体験してみようか!的なー

参照の値渡し

値渡しとか、参照渡しとか参照の値渡しとか色々あって、「Javaは参照の値渡しです」とか色々説明があるが、どうも参照渡しと参照の値渡しについてすっきりしなかった。
が、【参照の「値渡し」】と読むのではなく、【「参照の値」渡し】と読めばすっきりする気がしてきた。"参照の値" とは何だと言うと、C/C++での表現を借りるなら "ポインタ" とか "変数のアドレス(を格納する変数)" といったことか。なので高級言語では隠蔽されているポインタの概念を持ち出せるのであれば【「ポインタ」渡し】と読み替えると随分理解しやすくなる気がする。

値渡し

void hoge(int val /*仮引数*/)
{
    printf("%p", &val);
    val = 3;
    printf("%d\n", val);
}

int main(void)
{
    int val = 5;
    hoge(val /*実引数*/);
    printf("%p %d\n", &val, val);
    return 0;
}

参照渡し

void hoge(int& val)
{
    printf("%p", &val);
    val = 3;
    printf("%d\n", val);
}

int main(void)
{
    int val = 5;
    hoge(val);
    printf("%p %d\n", &val, val);
    return 0;
}

参照の値渡し

void hoge(int* val)
{
    printf("%p", val);
    int new_val = 3; // おっと...
    val = &new_val;  // 強引な書き方だなぁ...
    printf("%d\n", *val);
}

int main(void)
{
    int val = 5;
    hoge(&val);
    printf("%p %d\n", &val, val);
    return 0;
}

という感じかな。

コンパイラの中なんて読める技量もないのでこういう程度の解釈しかできないけど。
Javaのクラスインスタンスなどをメソッドの実引数として渡すと、そのポインタがメソッド内の仮引数として渡るような動きをするのだろう。

Java風味で考えると

Java風味コード

void java_method(SomeClass x)
{
    x = new SomeClass(); // すいませんが、ポインタに新しい変数のアドレスが入った!
    // なので、メソッドを抜けた世界ではxの元になった実引数の実体には影響ないよん
}

ということかな。
pythonで list を操作した時にうっかりはまった。

python的な

def hoge(li):
    li = [1, 2, 3] # 新しいリストのポインタが入っただけで実引数の実体に影響しない

def fuga(li):
    li[:] = [1, 2, 3] # これだとliがさす実体を操作するみたい...。仕様はよく知らん。

代入風味のことをする時は注意しないとなぁ。
しかし、[:] の完全な仕様はよく分からんな。というかスライスの実装の詳細と言う感じか。
listの内部表現が双方向リストのヘッドであると推測すれば、スライスとはヘッドにぶら下がってるノードの取捨選択であり、li[:] = [1, 2, 3] とは、ヘッドに [1, 2, 3] をぶら下げるって理解できるかな?
ヘッドそのものはすげ替えないから、メソッドを抜けても実引数側の実体にも影響が残るみたいな。
同じ代入演算子みたいに見えるけど、[:]を head()というメソッドだと思うと、

python的疑似コード

def hoge(li):
    li.=([1, 2, 3])

def fuga(li):
    li.head().=([1, 2, 3])

という感じで、実はhogeのケースではlistオブジェクトのイコール演算子で、fugaのケースではlistのヘッドオブジェクトのイコール演算子として動作していたとか。
演算子オーバーライドで確認すれば分かるかもしれんけど、案外難しいなぁ。

ただ、まぁ、少々間違っていても、こういう裏部隊を推測しながらコーディングして、インスタンスがどこで何回再生成されているだろうかとか考えるとパフォーマンス面の疑問も解消しやすいかなと思う。
rubyとかも破壊的メソッドでの操作だとインスタンスそのものが変わるので新たなメモリ確保が発生しないけど、非破壊的メソッドだと新規インスタンスの生成が走るからメモリの確保とコンストラクタの実行を伴うからねぇ。膨大なループの中で実行するとパフォーマンスに影響でるよね。

C言語はポインタが難しくて挫折した...」って場合でも、結局最近の言語を深く理解するには隠蔽されたアドレスの概念をつかまないといつか行き詰ることもあるんだろうなぁ。ポインタからは実は逃げられないんだなぁと思ってみる。
ポインタを陽に持たない言語で遠回しなポインタの解説を読むよりは、C/C++で直接ポインタを理解してそれを心に置きながら、非ポインタ系言語の動きを理解するほうが楽そうにも見える。

ま、知らんけどー