クラスメソッドとクラス変数

昨日まで調べていた ruby 1.9 の新機能、クラスローカルインスタンス変数が、最新のリビジョン(11813)では取り除かれている。
どうやら、この辺りの事情のようだ。


ごもっともなご意見である。


というわけで、今日はターゲットを変更して、クラス変数を調べてみることにした。

クラス変数

クラス変数は、クラスのオブジェクトから共通に参照できる変数だ。C++ の static メンバ変数と同じような働きをする。
ところで、話は変わるが、C++ の static というキーワードは文脈によって全然違う意味を持つような。クラス定義内で使われた場合は、クラスメソッドかクラス変数を意味するが、関数内で使われた場合は関数スコープの静的変数を意味する。トップレベルで使われた場合は、ファイルスコープの関数定義または変数定義となる。
よくもまあいろいろと使い回したもんだ、と思ったが、もしかしたら、関数と変数に対して、スコープとメモリの確保方法の2種類のパラメーターを決めてるだけか。たぶんそうに違いない。この「原理」を昔発見して、すごく嬉しかったのを覚えているような、ないような。


以上は余談。


Rubyインスタンス変数はオブジェクトの中にあるが、クラス変数はどこにあるんだろうか?
「クラスの中」と思った人、正解です。
Ruby ではクラスもオブジェクトなので、そのオブジェクトの中に、クラス変数は存在する。それもオブジェクトとしてのクラスの、インスタンス変数が格納されるのと同じテーブルに格納される。
ということは、クラス変数と、クラスのインスタンス変数は衝突しないんだろうか?
Ruby ではクラス変数とインスタンス変数の表記が違う。 (@@a vs. @a) そして、変数をテーブルに格納するときには @ を含めた名前をキーにして格納するので、同じテーブルを使っても名前の衝突は起こらないというのが解答。なかなかうまくできている。
この辺りの実装の説明は、RHG 第6章の「クラス変数」が詳しい。

クラスメソッドとクラス変数

クラス変数はクラスの変数、と言い切ってしまうと、とてもシンプルに聞こえる。ところがクラスメソッドがからんでくると、なかなか興味深いことになる。

下の3つのメソッド定義は、どれもクラス A のクラスメソッドを作成する。

class A
  @@a = "Hello"
  def A.test_in
    puts @@a
  end
end

def A.test_out
  puts @@a
end

class<<A
  def test_singleton
    puts @@a
  end
end

どのメソッドも A.test_XXX という形式で呼ぶことができるのだが、その結果がメソッドごと、また ruby のバージョンごとに微妙に違う。

A.test_in   
  # 1.8 -> Hello
  # 1.9 (r11842) -> Hello

A.test_out
  # 1.8 -> エラー uninitialized class variable @@a in Object (NameError)
  # 1.9 -> 警告 class variable access from toplevel 
         & エラー uninitialized class variable @@a in Object (NameError)

A.test_singleton
  # 1.8 -> 警告 class variable access from toplevel singleton method 
         & エラー uninitialized class variable @@a in Object (NameError)
  # 1.9 -> Hello

どれも同じように、クラスメソッドからクラス変数を参照しているのだが、結果は見てのとおりかなり違う。
何でこのようなことが起こるんだろう?これを調べるために ruby のコードを追ってみた。結果はまた後ほど。