Posted at

Ruby: インスタンス変数とクラス変数

More than 3 years have passed since last update.


インスタンス変数

インスタンス変数は「@」で始まる名前をしています。

インスタンス単位で持つ変数です。


sample-ivar.rb

class C

def foo
@count ||= 0
@count += 1

if @count < 3
puts "foo は呼ばないでください."
else
puts "foo は呼ぶなッ!!#{@count} 度目!!"
end
end
end

c1 = C.new
c1.foo
c1.foo
c1.foo

puts '-' * 8

c2 = C.new
c2.foo
c2.foo
c2.foo


実行結果

$ ruby sample-ivar.rb 

foo は呼ばないでください.
foo は呼ばないでください.
foo は呼ぶなッ!!3 度目!!
--------
foo は呼ばないでください.
foo は呼ばないでください.
foo は呼ぶなッ!!3 度目!!

c1 も c2 もそれぞれ 3度目でキレます。


クラス変数

クラス変数は「@@」で始まる名前をしてます。

クラス単位で持つ変数です。


sample-cvar.rb

class C

def foo
@@count ||= 0
@@count += 1

if @@count < 3
puts "foo は呼ばないでください."
else
puts "foo は呼ぶなッ!!#{@@count} 度目!!"
end
end
end

c1 = C.new
c1.foo
c1.foo
c1.foo

puts '-' * 8

c2 = C.new
c2.foo
c2.foo
c2.foo


実行結果

$ ruby sample-cvar.rb 

foo は呼ばないでください.
foo は呼ばないでください.
foo は呼ぶなッ!!3 度目!!
--------
foo は呼ぶなッ!!4 度目だ!!
foo は呼ぶなッ!!5 度目だ!!
foo は呼ぶなッ!!6 度目だ!!

c2 は最初からキレまくりです。(クラス通算3度目以降はキレます)


継承に関わるアレコレ


sample.rb

module M0

@@v = :M0
def foo ; "I am 'M0'" ; end
end

module MM
@@v = :MM
def foo ; "I am 'MM'" ; end
end

module M
include MM
@@v = :M
def foo ; "I am 'M'" ; end
end

module PM
@@v = :PM
def foo ; "I am 'PM'" ; end
end

class A
include M0
@@v = :A
def foo ; "I am 'A'" ; end
end

class B < A
@@v = :B
def foo ; "I am 'B'" ; end
end

class C < B
@@v = :C
def foo ; "I am 'C'" ; end
end

class D < C
include M
prepend PM
@@v = :D

def foo ; "I am 'D'" ; end

def getv
@@v
end
end

d = D.new
p d.class.ancestors #=> [PM, D, M, MM, C, B, A, M0, Object, Kernel, BasicObject]
p d.getv
p d.foo


上のスクリプトを実行すると以下のようになります。

$ ruby sample.rb 

[PM, D, M, MM, C, B, A, M0, Object, Kernel, BasicObject]
:D # メソッド getv の出力
"I am 'PM'" # メソッド foo の出力

(もっともな話ですが) foo の出力は予想通りでした。

現在は PM の foo が呼ばれていますが、それをコメントアウトすると次は D の foo が呼ばれます。

それをコメントアウトすると次は M の.....としていくと継承順(下図の緑線)をたどって最後は M0 の foo が呼ばれます。

継承関係図を以下に示します。(青箱:クラス、橙二重線箱:モジュール、緑線:ancestors、青線:superclass)

対して、「@@v」の値を出力する getv ですが、現在は D の@@vを参照しています。

これをコメントアウトすると......とやってみると以下の順番になります。

   D -> C -> B -> A -> M0 -> M -> MM -> PM   # クラス変数参照の順番

(これは図示しません。図を作ると美しくない図になります)

なんとなく、superclass をたどって折り返して来てインクルードされているモジュールを探索...、というようになっているようで、Ruby の仕様としては決まっているかも知れませんが、深く探求しません。

クラス変数に関しても、継承は複雑な問題をはらむようです。(で、強引にまとめます)


おわりに

本稿内容の動作確認は以下の環境で行っています。


  • Ruby 2.1.5 p273


付録

上の継承図のソース(Graphviz DOT形式)


sample.dot

digraph {

rankdir=LR;
node [shape = box];
node [color = blue] A B C D;
node [color = orange, peripheries = 2] M0 M MM PM;
D [style = filled, fillcolor = cyan];

D -> C [color = blue];
C -> B [color = blue];
B -> A [color = blue];

PM -> D [color = green];
D -> M [color = green];
M -> MM [color = green];
MM -> C [color = green];
C -> B [color = green];
B -> A [color = green];
A -> M0 [color = green];
}