Java の参照と C++ の参照の違い

kimuraya Javaでは、参照型を引数として渡す時は、C++で言う参照渡しに相当する処理を行っているようだな。そうでないと、Javaの動作は説明出来ない。
bleis @kimuraya よくある勘違いですけど違いますよ。Javaで参照型を引数として渡すときは、意味的にはC++で言うポインタ渡しに近いです。

数日前に関数引数の参照渡しの話が盛り上がっていたのですが,この話は今まで私も混同していました.TL を眺めていた時点では,説明している人達の言ってる事がよく理解できていなかったのですが,後で下記のコードを実行したところ,この時に言われていた事が少し理解できました.

class Foo {
    public Foo(String s) {
        message = s;
    }
    
    public String toString() {
        return message;
    }

    private String message;
}

class ExecFoo {
    public static void main(String args[]) {
        Foo x = new Foo("foo");
        System.out.println(x);
        ExecFoo.f(x, "bar");
        System.out.println(x);
    }
    
    public static void f(Foo dest, String message) {
        dest = new Foo(message);
    }
}
[clown@stinger example]$ javac Foo.java ExecFoo.java 
[clown@stinger example]$ java ExecFoo
foo
foo

ここの認識が曖昧だったのですが,Java では関数の引数は(原則?)全て値渡しで行われているようです.ただし,POD 以外の型(new で生成するオブジェクト)はそのオブジェクトへの「参照値」(アドレス,と言う認識で正しい?)が渡されているため,その「参照値」を介して参照先のオブジェクトの状態を変更できる,と言う形になっているようです.そのため,渡された引数自体に何らかの値を代入したとしても,その代入は呼び元には反映されません.

一方で,C++ の関数での参照型の引数は,参照渡しであるため渡された引数自体に何らかの値を代入すると呼び元にも反映されます.下記のコードは,比較としては適切ではありませんでした.詳細は,追記を参照.

#include <iostream>
#include <string>

class Foo {
public:
    explicit Foo(const std::string& s) :
        message(s) {}
    
    const std::string& to_string() const {
        return message;
    }

private:
    std::string message;
};

void f(Foo& dest, const std::string& message) {
    dest = Foo(message);
}

int main(int argc, char* argv[]) {
    Foo x("foo");
    std::cout << x.to_string() << std::endl;
    f(x, "bar");
    std::cout << x.to_string() << std::endl;
    
    return 0;
}
[clown@stinger example]$ g++ -Wall ex_ref.cpp 
[clown@stinger example]$ ./a
foo
bar

私自身も今までずっと「Java の関数の引数は C++ で言うところの参照型によるものと同じ.ただし,POD のみ値渡し」と言う認識だったので,この辺りは注意しておかないと誤解しやすい話だなぁと感じました.

追記

C++版の f() にある
dest = Foo(message);
で何が実際に行われるかを勘違いしておられるように見えるのだけれど。

C++の参照渡しは Javaと違うのか? - ブログ - ワルブリックス株式会社

コメントでも指摘されていますが,確かに先に紹介した C++ のコードをもって「Java の参照と C++ の参照は違う」と言うのは間違いでした.C++ の暗黙の=演算子の定義を考えると C++ の例は以下のコードに近いものになり,これでは Java の参照と C++ のそれは違うと言う事を証明している事にはなりません.

import java.io.*;

class Foo {
    public Foo(String s) {
        message = s;
    }
    
    public String toString() {
        return message;
    }
    
    public void assign(Foo rhs) {
        message = rhs.message;
    }
    
    private String message;
}

class ExecFoo {
    public static void main(String args[]) {
        Foo x = new Foo("foo");
        System.out.println(x);
        ExecFoo.f(x, "bar");
        System.out.println(x);
    }
    
    public static void f(Foo dest, String message) {
        Foo tmp = new Foo(message);
        dest.assign(tmp);
    }
}

指摘された後にどういう例が良いのかなぁと考えていたのですが,Java の参照と C++ の参照は違うと言う事が言いたい場合は,下記のような例が良いのかなと思いました.

#include <iostream>
#include <string>

class Foo {
public:
    explicit Foo(const std::string& s) :
        message(s) {}
    
    const std::string& to_string() const {
        return message;
    }

private:
    std::string message;
};

void f(Foo*& dest, const std::string& message) {
    if (dest) delete dest; // Java の場合は,この辺は GC が上手い事やってくれる.
    dest = new Foo(message);
}

int main(int argc, char* argv[]) {
    Foo* x = new Foo("foo");
    std::cout << x->to_string() << std::endl;
    f(x, "bar");
    std::cout << x->to_string() << std::endl;
    
    if (x) delete x;
    return 0;
}

C++ の例はあまり深く考えてなかったのですが,例を示すのもなかなか難しいものだと実感しました.