类变量的范围

在两个BC类中设置一个类变量@@foo ,其中两个类都不是另一个的子类,但它们都包含一个公共模块A ,似乎分别为BC创建@@fooA无法访问:

 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 

但是,如果在A分配@@foo ,它作为BC的祖先,那么BC访问的@@foo就成为A@@foo

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

BC@@foo怎么了? 当它的任何祖先的@@foo被分配时它们是否被删除?

此代码出现在MRI的variable.c rb_cvar_setrb_cvar_get中:

 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是变量名的C内部表示( @@foo )。

front当前正在访问变量的类( B / C )。

target最远的祖先 ,变量也曾被定义过A )。

如果fronttarget不相同 ,Ruby会警告class variable #{id} of #{front} is overtaken by #{target}

然后front的RCLASS_IV_TBL中逐字删除变量名 ,以便在后续查找中,搜索该变量名称“通过”“冒泡”到定义变量的最远祖先。


请注意,此检查和删除不仅发生在cvar gets上,还发生在集合上:

 $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 B中设置的值1 覆盖 ,我们仍然会收到相同的警告 :它的B类变量被A超越!

虽然普通的Ruby编码器通常会发现他们的变量的值在各种各样的,也许是意想不到的地方发生变化(即“父母”/“祖父母”/“叔叔”/“堂兄”/“姐妹”)更令人惊讶模块和类),触发器和措辞都表明该警告实际上是为了告知编码器该变量的“真实来源”已经改变。

我的下面的笔记来自Metaprogramming Ruby(由Paolo Perrotta撰写) ,正如我遇到你的问题时,我正好在阅读。 我希望这些摘录(页码将在括号中)和我的解释对你有所帮助。

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

Class实例变量属于Class类的对象,只能由类本身访问 – 而不是由实例或子类访问。 (106)

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

以下是作者的一个例子:

 @@v = 1 class MyClass @@v = 2 end @@v # => 2 

你得到这个结果是因为类变量并不真正属于类 – 它们属于类层次结构 。 由于@@ v是在main的上下文中定义的,因此它属于main'sObject …以及Object所有后代。 MyClassinheritance自Object ,因此它最终共享相同的类变量。 (107)

但是,因为您的具体问题不仅要与课程有关,还要与模块有关:

当你在一个类中包含一个模块时,Ruby将创建一个匿名类,它包装模块并在链中插入匿名类,就在包含类本身之上。 (26)

所以,当你看到B.ancestors ,你会看到:

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

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

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

如果我们记住类变量属于类层次结构,那么类变量@@foo ,只要它在Module A定义(因此,只要B包含A ,就会创建B之上的匿名类) ,将属于B (也属于C ,因为它包括A )。

简而言之:

  1. @@foo仅在BC定义时(但不在A ),则B有一个类变量@@foo ,它与C的类变量@@foo不同。 这是因为类变量只能被该类和所有后代访问。 但是BC通过他们的祖先A而不是通过他们的后代相关联。
  2. 一旦在A中定义了@@foo ,该类变量就会被A的所有后代inheritance – 即BC 从现在开始, B类中对@@foo的引用实际上是引用属于A的类变量。 在B中定义的原始@@foo已被覆盖替换(由其祖先接管)。 C@@foo也是如此。 BC都可以写入和读取相同的类变量@@foo ,因为它属于它们的共同祖先A

此时, ABC任何A都可以修改@@foo 。 例如:

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