2009-01-27 146 views
37

是否有任何理由對與基類不同的被重寫的C++虛函數授予權限?這樣做有沒有危險?使用C++中的私有函數覆蓋公共虛函數

例如:

class base { 
    public: 
     virtual int foo(double) = 0; 
} 

class child : public base { 
    private: 
     virtual int foo(double); 
} 

C++ faq說,這是一個壞主意,但沒有說爲什麼。

我在一些代碼中看到了這個習慣用法,我相信作者試圖讓這個類成爲最終的,基於一個假設,即不可能重載一個私有成員函數。但是,This article顯示了重寫私有函數的示例。當然another part of the C++ faq建議不要這樣做。

我的具體問題:

  1. 是否有使用虛擬方法不同的權限在派生類VS基類的任何技術問題?

  2. 有沒有合理的理由這樣做?

+3

重新發明「保護」是我們嗎? – 2009-01-27 18:27:20

回答

27

問題是基類方法是它聲明接口的方法。實質上說,「這些是你可以對這個班級的對象做的事情。「

當你在一個派生類中創建了一些Base聲明爲public private的東西時,你正在帶走一些東西,現在,即使Derived對象是」is-a「Base對象,你應該可以做的事情到一個基類對象,你不能這樣做派生類對象,打破Liskov Substitution Prinicple

這是否會導致您的程序中的「技術」問題?也許沒有,但它可能意味着你的類的對象不會表現在你的用戶期望他們的行爲方式

如果你發現自己在這是你想要的情況下(除了在另一個答案中提到的廢棄方法的情況),機會你是否有一種繼承模型,其中繼承不是真正建模的「is-a」(例如, Scott Myers的示例Square從Rectangle繼承,但不能改變Square的寬度而不考慮它的高度,就像你可以爲一個矩形一樣),你可能需要重新考慮你的類關係。

6

沒有任何技術問題,但最終會出現這樣一種情況,即公開可用的函數將取決於您是否具有基礎指針或派生指針。

這在我看來會很奇怪和令人困惑。

35

你確實得到了令人驚訝的結果:如果你有一個孩子,你不能調用foo,但是你可以把它投到一個基地然後調用foo。

child *c = new child(); 
c->foo; // compile error (can't access private member) 
static_cast<base *>(c)->foo(); // this is fine, but still calls the implementation in child 

我想你也許可以圖謀一個例子,你不想暴露的功能,當你把它當作基類的一個實例時除外。但事實表明,這種情況會出現,這意味着沿線可能會重構一個糟糕的OO設計。

+0

Qt做到了,並把我帶到這裏:)謝謝。 – mlvljr 2012-08-29 23:00:28

+3

另外:作爲一個IDE,你吸! – mlvljr 2012-08-29 23:00:50

3

它可以做到,偶爾會帶來好處。例如,在我們的代碼庫中,我們使用的庫包含一個帶有我們曾經使用過的公共函數的類,但現在由於其他潛在問題(有更安全的調用方法)而阻止使用它。我們也碰巧有一個從這個類派生出來的類,我們很多代碼都直接使用它。所以,我們讓派生類中的給定函數是私有的,以幫助每個人都記住,如果他們能夠幫助它,就不要使用它。它並沒有消除使用它的能力,但它會在代碼嘗試編譯時捕獲一些用途,而不是稍後在代碼評論中使用。

1
  1. 沒有技術問題,如果您的意思是技術上存在隱藏的運行時成本。
  2. 如果你公開繼承基地,你不應該這樣做。如果您通過受保護或私有繼承,那麼這可以幫助防止使用無意義的方法,除非您有基指針。
4

如果您正在使用私有繼承 - 即您想要重用基類的(自定義)功能而非接口,它可能非常有用。

1

私有繼承的一個很好的用例是Listener/Observer事件接口。對於私有對象

示例代碼:

class AnimatableListener { 
    public: 
    virtual void Animate(float delta_time); 
}; 

class BouncyBall : public AnimatableListener { 
    public: 
    void TossUp() {} 
    private: 
    void Animate(float delta_time) override { } 
}; 

對象的一些用戶希望父功能和一些希望子功能:

class AnimationList { 
    public: 
    void AnimateAll() { 
     for (auto & animatable : animatables) { 
     // Uses the parent functionality. 
     animatable->Animate(); 
     } 
    } 
    private: 
    vector<AnimatableListener*> animatables; 
}; 

class Seal { 
    public: 
    void Dance() { 
     // Uses unique functionality. 
     ball->TossUp(); 
    } 
    private: 
    BouncyBall* ball; 
}; 

這樣的AnimationList可以容納一個參考父母並使用父母功能。雖然Seal持有對孩子的引用,並使用獨特的孩子功能並忽略父母的功能。在這個例子中,Seal不應該叫Animate。現在,如上所述,可以通過投射到基礎對象來調用Animate,但是這更困難並且通常不應該完成。

相關問題