2012-07-24 70 views
33

爲什麼這個編譯:訪問基類的保護成員在另一個子

class FooBase 
{ 
protected: 
    void fooBase(void); 
}; 

class Foo : public FooBase 
{ 
public: 
    void foo(Foo& fooBar) 
    { 
     fooBar.fooBase(); 
    } 
}; 

但是這不?

class FooBase 
{ 
protected: 
    void fooBase(void); 
}; 

class Foo : public FooBase 
{ 
public: 
    void foo(FooBase& fooBar) 
    { 
     fooBar.fooBase(); 
    } 
}; 

一方面,C++允許訪問該類的所有實例私有/ protected成員,但在另一方面,它不授予訪問某個子類的所有實例的基類的保護成員。 這對我來說看起來頗不一致。

我已經用VC++和ideone.com測試了編譯,並且都編譯了第一個而不是第二個代碼片段。

+0

@iammilind你確定你正在關閉正確的問題嗎?或者它應該是另一種方式? – Eiko 2016-08-02 13:24:38

+0

@Eiko最初我先關閉了另一個。然後我發現現在的答案比標準更詳細和引用。因此重新開放並關閉了這個。 – iammilind 2016-08-02 14:09:29

回答

28

foo收到FooBase參考,編譯器不知道這個說法是否是Foo的後代,所以它必須假設它不是。 Foo可以訪問其他Foo對象的繼承保護成員,而不是所有其他兄弟類。

考慮以下代碼:

class FooSibling: public FooBase { }; 

FooSibling sib; 
Foo f; 
f.foo(sib); // calls sib.fooBase()!? 

如果Foo::foo可以調用任意FooBase後代的保護成員,那麼它可以調用的FooSibling的保護方法,它具有Foo沒有直接關係。這不是受保護的訪問應該如何工作。

如果Foo需要訪問所有FooBase對象的保護成員,而不僅僅是那些也被稱爲是Foo後代,然後Foo需要是FooBase的朋友:

class FooBase 
{ 
protected: 
    void fooBase(void); 
    friend class Foo; 
}; 
+0

啊,用你的例子代碼的原因,爲什麼它不被允許,是非常明顯的。謝謝。 – Kaiserludi 2012-07-24 14:25:17

+0

我不明白你的例子。如果fooBase不是虛擬的,你得到的是FooBase :: fooBase,這正是你指出的。如果fooBase是虛擬的,你實際上調用了FooSibling :: fooBase,但這也是你使用虛擬函數的原因:能夠使函數適應實際的對象?我沒有看到這種行爲何時出現問題。 – 2016-02-01 17:27:36

+0

虛擬無關在這裏@Vincent。成員的虛擬身份不會影響誰被允許使用其名稱。成員的*可見性*決定誰可以使用其名稱。 'Foo'可以看到其他已知的'Foo'對象的受保護成員。它看不到任何其他對象的受保護成員,甚至不能看到與其祖先類相關的對象,因爲這些祖先不需要*已知*爲'Foo'。虛擬性和可視性是C++中的正交概念(但不一定在其他語言中)。 – 2016-02-01 18:27:29

3

在這兩個示例中,Foo都繼承了受保護的方法fooBase。但是,在第一個示例中,您嘗試訪問同一類中給定的受保護方法(Foo::foo調用Foo::fooBase),而在第二個示例中,您嘗試從未聲明爲朋友類的另一個類訪問受保護的方法(Foo::foo嘗試致電FooBase::fooBase,失敗,後者受保護)。

1

在第一個例子,你傳遞一個Foo類型的對象,它顯然繼承了fooBase()方法,因此可以調用它。在第二個示例中,您試圖調用受保護的函數,無論在哪種情況下,您都無法從其聲明的類實例中調用受保護的函數。 在第一個例子中,你繼承了受保護的方法fooBase,所以你有權在WITHIN Foo中調用它。

1

我傾向於用概念和消息來看事物。如果你的FooBase方法實際上被稱爲「SendMessage」,Foo是「EnglishSpeakingPerson」而FooBase是SpeakingPerson,你的受保護的聲明旨在限制SendMessage到英語發音人(和子類,例如:AmericanEnglishSpeakingPerson,AustralianEnglishSpeakingPerson)之間。另一種從SpeakingPerson派生的FrenchSpeakingPerson將無法接收SendMessage,除非您將FrenchSpeakingPerson聲明爲朋友,其中'friend'表示FrenchSpeakingPerson具有從EnglishSpeakingPerson接收SendMessage的特殊功能(即可以理解英語)。

9

關鍵是protected授予您訪問您自己的副本的成員,而不是的任何其他對象的成員。這是一個常見的誤解,因爲更多的情況下,我們總結並指出protected授予對派生類型成員的訪問權限(沒有明確說明只對自己的基地...)

現在,這是一個原因,並且通常不應該訪問層次結構的不同分支中的成員,因爲您可能會破壞其他對象所依賴的不變量。考慮到在該緩存以下不同的策略的結果一些大的數據成員(受保護的)和兩個派生類型執行一個昂貴的計算類型:

class base { 
protected: 
    LargeData data; 
// ... 
public: 
    virtual int result() const;  // expensive calculation 
    virtual void modify();   // modifies data 
}; 
class cache_on_read : base { 
private: 
    mutable bool cached; 
    mutable int cache_value; 
// ... 
    virtual int result() const { 
     if (cached) return cache_value; 
     cache_value = base::result(); 
     cached = true; 
    } 
    virtual void modify() { 
     cached = false; 
     base::modify(); 
    } 
}; 
class cache_on_write : base { 
    int result_value; 
    virtual int result() const { 
     return result_value; 
    } 
    virtual void modify() { 
     base::modify(); 
     result_value = base::result(); 
    } 
}; 

cache_on_read型捕獲修改對數據和標記結果爲無效,以便下一次讀取的值重新計算。如果寫入次數相對較高,這是一個很好的方法,因爲我們只根據需求執行計算(即多次修改不會觸發重新計算)。 cache_on_write預先預先計算出結果,如果寫入次數很少,並且您希望讀取的確定性成本(認爲讀取延遲較低),則這可能是一種很好的策略。

現在回到原來的問題。兩種緩存策略都保持比基本更嚴格的不變量集合。在第一種情況下,如果data在上次讀取後未被修改,則額外不變量爲cachedtrue。在第二種情況下,額外的不變量是result_value是操作在任何時候的值。

如果第三個派生類型引用base並訪問data來編寫(如果protected允許),那麼它將與派生類型的不變量斷開。

這就是說,語言的規範是破碎(個人意見),因爲它留下後門來實現該特定結果。特別是,如果在派生類型中創建指向基元成員的指針,則會在derived中檢查訪問權限,但返回的指針是指向成員base的指針,該指針可應用於任意base對象:

class base { 
protected: 
    int x; 
}; 
struct derived : base { 
    static void modify(base& b) { 
     // b.x = 5;      // error! 
     b.*(&derived::x) = 5;    // allowed ?!?!?! 
    } 
} 
19

C++ FAQ很好地總結了這個問題:

[你]被允許選擇自己的口袋裏,但你不能來接你父親的口袋裏,也沒有你哥哥的腰包。

+8

你可以選擇你自己的以及你的兒子的口袋 – 2014-06-20 14:34:57

0

除了hobo's answer,您可能會尋求解決方法。

如果您希望子類想要調用fooBase方法,您可以使其成爲static。具有所有參數的子類可以訪問靜態保護方法。

相關問題