2014-02-20 159 views
5

的设置一类在两个类BC可变@@foo,其中既不是其他的子类,但它们都包括一个公用模块A,似乎单独创建@@fooBC,这A不能访问:范围类变量

module A; end 
class B; include A; @@foo = 1 end 
class C; include A; @@foo = 2 end 

module A; p @@foo end # => NameError: uninitialized class variable @@foo in A 
class B; p @@foo end # => 1 
class C; p @@foo end # => 2 

但当@@fooA分配,其工作方式的祖先都BC,在@@fooBC访问成为A@@foo

module A; @@foo = 3 end 
class B; p @@foo end # => 3 
class C; p @@foo end # => 3 

发生了什么事的BC@@foo?当它的祖先的任何@@foo被分配时,它们是否被删除?

+0

@ArupRakshit这不是答案。其实,我的问题是出于这个答案。你的回答是这个问题的出发点。 – sawa

+0

我得到了..让我想想.. :-)好问题。 –

回答

2

此代码出现在两个rb_cvar_setrb_cvar_get在MRI的variable.c

if (front && target != front) { 
    st_data_t did = id; 

    if (RTEST(ruby_verbose)) { 
     rb_warning("class variable %"PRIsVALUE" of %"PRIsVALUE" is overtaken by %"PRIsVALUE"", 
      QUOTE_ID(id), rb_class_name(original_module(front)), 
      rb_class_name(original_module(target))); 
    } 
    if (BUILTIN_TYPE(front) == T_CLASS) { 
     st_delete(RCLASS_IV_TBL(front),&did,0); 
    } 
} 

id是变量名(@@foo)的C-内部表示。

front是其中变量当前访问类(B/C)。

target最远的祖先,其中变量具有也曾经被定义A)。

如果fronttarget是不一样的,红宝石警告class variable #{id} of #{front} is overtaken by #{target}

变量名然后字面上删除front的RCLASS_IV_TBL,使得在随后的查找,该变量名搜索‘下落通过’在‘冒泡’到最远的祖先变量被定义。


注意,此检查和删除发生不只是在CVaR的获得,但台以及:

$VERBOSE = true 

module A; end 
class B; include A; @@foo = 1; end # => 1 

module A; @@foo = 3 end # => 3 
class B; p @@foo = 1 end # => 1 
#=> warning: class variable @@foo of B is overtaken by A 


module A; p @@foo end # => 1 

在这个例子中,即使它的A的价值3由价值1被覆盖B被设置,我们仍然REC eive相同的警告它的B的类变量被A超越!虽然通常对于普通的Ruby编码器来说,发现其变量的值在各种可能意想不到的地方(即在“父”/“祖父母”/“叔叔”/“表兄弟”中)变化通常更令人惊讶。 /“姐姐”模块和类),触发器和措辞都表明警告实际上是意在告知该变量的“真理之源”改变了编码器。

+1

谢谢你的好回答。最后一行的'A'的结果是令我惊讶的。在'A'中存在一个分配'3'的行,与最后一行不同。因此,为了改写它,似乎(1)设置和获取可以通过祖先层次向上或向下传递,但是(2)仅在变量存在的域内,以及(3)存在可以是唯一的向下继承,(4)删除层次结构中非最顶层的变量。 – sawa

+0

你真棒+1 –

1

我在下面的注释取自Metaprogramming Ruby (by Paolo Perrotta),当我碰到你的问题时,我碰巧正在阅读。我希望这些摘录(页码在括号内),我的解释对你很有帮助。

请记住,类变量不同于类实例变量

一个类的实例变量属于Class类的对象,并且是 仅由类本身可访问的 - 而不是由一个实例或通过 子类。 (106)

类变量,在另一方面,属于类别层次。这意味着它属于任何阶级以及该阶级的所有后代。

下面是笔者的例子:

@@v = 1 

class MyClass 
    @@v = 2 
end 

@@v # => 2 

你得到这样的结果,因为类变量并不真正属于 类 - 它们属于类层次。由于@@ v在 上下文main中定义,所以它属于main's类别Object ...和 Object的所有后代。 MyClass继承自Object,所以 它最终共享相同的类变量。 (107)

而且由于你的具体问题,需要做不仅与类,但也与模块:

当您在一个类模块,红宝石会创建一个匿名 类包装模块并将匿名类插入 链中,位于包含类本身之上。(26)

所以,你看B.ancestors,您将看到:

=> [B, A, Object, Kernel, BasicObject] 

同样,对于C.ancestors,您将看到:

=> [C, A, Object, Kernel, BasicObject] 

如果我们记住,类变量属于类层次结构,然后类变量@@foo,只要它在Module A中定义(因此,只在B附近的匿名类t只要B包括A),将属于B(并且还包含C,因为它包括A)。

简单地说:

  1. @@fooBC才被定义(但不是在A),然后B有一个类变量@@foo这是比类C变量@@foo不同。这是因为类变量只能被该类和所有后代访问。但BC通过他们的祖先A相关,而不是通过他们的后代。
  2. 只要@@fooA中定义,那个类变量就会被所有A的后代 - 即BC继承。从这里开始,对类B中的@@foo的引用实际上是引用属于A的类变量。在B中定义的原始@@foo已被覆盖替换(由其祖先接管)。 @@fooC中也发生过同样的情况。 BC都可以写入和读取相同的类变量@@foo,因为它属于它们的共同祖先A

在这一点上,AB,或C任何人都可以修改所有@@foo。例如:

class B 
    p @@foo # => 3 
    @@foo = 1 
end 

module A 
    p @@foo # => 1 
end 
+0

在想你写的答案部分是:'这是在B中定义的原@@富已被覆盖(接管它的祖先).'。我不认为表达“覆盖”是准确的。这意味着还有这样的变数。 – sawa

+0

这是真的。 *覆盖*可能不是最好的单词。也许* *代替,是一个更好的词 - 由属于不同(祖先)类完全不同的变量替换,但具有相同的名称。 –