【问题标题】:Scope of class variable类变量的范围
【发布时间】:2014-03-21 09:41:32
【问题描述】:

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

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 中时,它作为BC 的祖先,@@fooBC 访问成为@ 的@@foo 987654338@.

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

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

【问题讨论】:

  • @ArupRakshit 这不是答案。实际上,我的问题来自那个答案。你的回答就是这个问题的起点。
  • 我明白了.. 让我想想.. :-) 好问题。

标签: ruby class-variables


【解决方案1】:

此代码出现在 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 是变量名 (@@foo) 的 C 内部表示。

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

target最远的祖先,其中也曾经定义过变量 (A)。

如果fronttarget 不相同,Ruby 会警告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 中设置,我们仍然收到相同的警告B 的类变量被A 取代!

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

【讨论】:

  • 感谢您的好回答。您最后一行中A 的结果1 让我感到惊讶。在A 中分配3 的行的存在与最后一行不同。因此,换个说法,似乎(1)设置和获取可以向上或向下通过祖先层次结构,但是(2)仅在变量存在的域内,以及(3)存在只能是向下继承,并且 (4) 层次结构中的非最顶层变量被删除。
【解决方案2】:

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

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

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

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

这是作者的一个例子:

@@v = 1

class MyClass
  @@v = 2
end

@@v    # => 2

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

而且,由于您的具体问题不仅与类有关,还与模块有关:

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

所以,当您查看 B.ancestors 时,您会看到:

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

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

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

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

简单地说:

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

此时ABC中的任何一个都可以修改@@foo。例如:

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

module A
  p @@foo  # => 1
end

【讨论】:

  • 想想你写的答案部分是:The original @@foo which was defined in B has been overwritten (taken over by its ancestor).。我不认为“覆盖”这个表达是准确的。这意味着仍然存在这样的变量。
  • 确实如此。 覆盖可能不是最好的词。也许 replaced 是一个更好的词 - 被一个完全不同的变量代替,该变量属于不同的(祖先)类,但具有相同的名称。
猜你喜欢
  • 1970-01-01
  • 2021-02-08
  • 2014-04-18
  • 1970-01-01
  • 2013-01-16
  • 2012-05-10
  • 2022-01-11
  • 2016-06-07
相关资源
最近更新 更多