在两个类@@foo
和B
中设置一个类变量C
,其中两个类都不是另一个的子类,但它们都包含一个公共模块A
,似乎单独为@@foo
和B
创建C
,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
但是,@@foo
分配了A
,B
作为C
和@@foo
的祖先,B
C
@@foo
并且A
访问权限成为module A; @@foo = 3 end
class B; p @@foo end # => 3
class C; p @@foo end # => 3
的{{1}}。
@@foo
B
和C
的{{1}}发生了什么?它们的任何祖先@@foo
被分配时是否被删除?
答案 0 :(得分:2)
这段代码同时出现在MRI rb_cvar_set
的{{1}}和rb_cvar_get
中:
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
)的C内部表示。
@@foo
是当前正在访问的变量的类(front
/ B
)。
C
是最遥远的祖先,其中变量也已经定义(target
)。
如果A
和front
不一样,Ruby会警告target
。
变量名称从class variable #{id} of #{front} is overtaken by #{target}
的RCLASS_IV_TBL逐字删除,以便在后续查找中,搜索该变量名“通过”或“冒泡”到定义变量的最远祖先。
请注意,此检查和删除不仅发生在cvar上,而且发生在
上front
在此示例中,即使它的 $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
的{{1}}类变量被B
超越!
虽然普通的Ruby编码器通常会发现他们的变量值在不同的,可能是意外的地方(即“父母”/“祖父母”/“叔叔”/“堂兄”/ “姐妹”模块和类),触发器和措辞都表明警告实际上是为了通知编码人员变量的“真实来源”已经改变。
答案 1 :(得分:1)
下面我的笔记取自Metaprogramming Ruby (by Paolo Perrotta),正好在我遇到你的问题时,我正好在读书。我希望这些摘录(页码放在括号中)和我的解释对你有帮助。
请注意,类变量与类实例变量不同。
Class实例变量属于类
Class
的对象,并且是 只能由类本身访问 - 不是通过实例或通过实例访问 子类。 (106)
另一方面,类变量属于类层次结构。这意味着它属于任何类以及该类的所有后代。
以下是作者的一个例子:
@@v = 1
class MyClass
@@v = 2
end
@@v # => 2
你得到这个结果,因为类变量并不真正属于 类 - 它们属于类层次结构。由于@@ v在中定义
main
的上下文,它属于main's
类Object
...以及 所有Object
的后代。MyClass
继承自Object
,所以 它最终共享相同的类变量。 (107)
但是,由于您的具体问题不仅要与课程有关,还要与模块有关:
当你在一个类中包含一个模块时,Ruby会创建一个匿名的 包装模块并在其中插入匿名类的类 链,就在包含类本身之上。 (26)
因此,当您查看B.ancestors
时,您会看到:
=> [B, A, Object, Kernel, BasicObject]
同样,对于C.ancestors
,您会看到:
=> [C, A, Object, Kernel, BasicObject]
如果我们记住类变量属于类层次结构,那么类变量@@foo
,只要它在Module A
中定义(等等,匿名类就在{{1}之上B
包含B
)后立即创建的内容将属于A
(以及B
,因为它包含C
)。< / p>
简单地说:
A
仅在@@foo
和B
中定义(但不在C
中),A
有一个类变量B
}与@@foo
中的类变量@@foo
不同。这是因为类变量只能被该类和所有后代访问。但是C
和B
通过他们的祖先C
相关联,而不是通过他们的后代。A
中定义了@@foo
,该类变量就会被A
的所有后代继承 - 即A
和B
。从现在开始,类C
中对@@foo
的引用实际上是引用属于B
的类变量。 A
中定义的原始@@foo
已被B
中的@@foo
也发生了同样的情况。 C
和B
可以同时写入和读取同一个类变量C
,因为它属于它们的共同祖先@@foo
。此时,A
,A
或B
中的任何人都可以修改C
。例如:
@@foo