Ruby は基本的にすべてがオブジェクトで、またすべてが「参照の値渡し」です。ただし数値とシンボルは immutable なので、「値渡し」のように振舞います。
注意すべきは文字列です。
a = "Hello" p a.object_id #=>23440680 b = a p b.object_id #=>23440680 b += ", world!" p b #=>"Hello, world!" p b.object_id #=>23440560 p a #=>"Hello" p a.object_id #=>23440680
b = a
をやっていますが、b += ", world!"
のところで別のオブジェクトが生成されているので、a
を出力しても "Hello"
になります。一見「値渡し」のようです。けれども、
a = "hello" p a #=>"hello" b = a b.capitalize! p a #=>"Hello"
の場合、変更を加えたのは変数b
ですが、capitalize!
メソッドは別のオブジェクトを生成しないので、変数a
も変更されるように見えます。これが Ruby が「参照の値渡し」である証拠です。特にメッソド内で(引数によって与えられた)文字列を操作する場合は、注意が必要になります。気になる場合は、dup
メソッドでオブジェクトをコピーしておけば安全です。あるいは、不用意に変更したくないオブジェクトは、freeze
しておくかです。
配列の場合でも同様です。
a = [0, 1] p a #=>[0, 1] b = a b.push(2) p a #=>[0, 1 ,2]
これを防ぎたければ、b = a
の代りに b = a.dup
としなければなりません。(※注意)
自分で作ったクラスのオブジェクトも、当然すべて「参照の値渡し」です。
※注意
『Effective Ruby』(邦訳p.62)にこうあります。
Array のようなコレクションクラスの場合、これは[dup や clone では]コンテナのコピーは作られるが、要素のコピーは作られないということだ。オリジナルのコレクションに影響を与えずに要素を追加、削除はできるが、要素自体についてはそうは言えないのである。オリジナルのコレクションとコピーは、ともに同じ要素オブジェクトを参照している。要素を書き換えると、両方のコピーからもそれが見えてしまう。
つまり、こういうことです。
a = ["Polar"] b = a.dup << "Bear" b.each {|x| x.sub!('lar', 'oh')} p a #=>["Pooh"]
こうした場合には「浅い」コピーの dup
は無力なので、自分で「深い」コピーの処理を書かねばなりません(ちなみに、ここで配列a
を freeze
してもエラーは出ません)。前掲書には、Marshal
モジュールを使った、多少トリッキーな対処法が載っています(b = Marshal.load(Marshal.dump(a))
)。配列の深いコピーをする組み込みメソッドがないのは、あまりない操作だし、素直に配列の深いコピーをするとメモリを大幅に喰うからでしょうかね。
Marshal
を使わなければ、こんなふうにやったらどうでしょうか。キモは2行目です。
a = ('a'..'d').to_a b = a.map {|x| x.dup} #深いコピー p b.map {|x| x.upcase!} #=>["A", "B", "C", "D"] p a #=>["a", "b", "c", "d"]
ただ、多次元配列とかだと、ダメかも知れない。そのうち調べてみます。(6/14追記)
3年後の追記
上の説明はまだあまりわかっていない感じですが、一応そのままにしておきます。ただ、
Ruby は基本的にすべてがオブジェクトで、またすべてが「参照の値渡し」です。ただし数値とシンボルは immutable なので、「値渡し」のように振舞います。
というのはやはり正しいです。
メソッド呼び出しは「参照の値渡し」であることに注意が必要です。特に配列や文字列を引数にしてメソッド内部でオブジェクトの「破壊的変更」をすると、メソッド呼び出しの方でも値が変ってしまいます。文字列の例。
def cp(string) p string.capitalize! end a = "tokyo" cp(a) #=>"Tokyo" p a #=>"Tokyo"
配列の例。
def double(ar) ar[1] *= 2 return ar end ar = [2, 4, 6] p double(ar) #=>[2, 8, 6] p ar #=>[2, 8, 6]
代入も「参照の値渡し」です。
a = "kyoto" b = a a.capitalize! p b #=>"Kyoto"
破壊的変更でないメソッドを使うと、別のオブジェクトが生成されます。
a = "kyoto" b = a.capitalize + " University" #=>これらは破壊的変更でない p a.upcase! #=>"KYOTO" p b #=>"Kyoto University" p a.object_id #=>47415064424700 p b.object_id #=>47415064464080
以下のちがい、わかりますか? 与える出力は同じですが、大きくちがう部分があります。
a = "nagoya" a.capitalize! p a #=>"Nagoya" b = "nagoya" b = b.capitalize p b #=>"Nagoya"
変数a
はメソッド処理ののちでも object id が変わりません。しかし、変数b
はメソッド処理して再代入すると object id が変わります。これは大きなちがいです。これがわからないと、奇妙なバグを作ることがあるので注意しなければなりません。(2018/7/10)