2014-02-20 102 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

這是真的。 *覆蓋*可能不是最好的單詞。也許* *代替,是一個更好的詞 - 由屬於不同(祖先)類完全不同的變量替換,但具有相同的名稱。 –