2010-01-31 92 views
81

在C++中創建私有方法虛擬的優勢是什麼?C++中的私有虛擬方法

我曾經在一個開源的C注意到了這一點++項目:

class HTMLDocument : public Document, public CachedResourceClient { 
private: 
    virtual bool childAllowed(Node*); 
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&); 
}; 
+5

我認爲這個問題是倒退的。讓事物變成虛擬的原因總是一樣的:允許派生類來覆蓋它。所以問題應該是:使虛擬方法具有私密性的優點是什麼?答案是:默認情況下一切都是私有的。 :-) – ShreevatsaR 2014-04-02 16:58:29

+0

@ShreevatsaR但是你甚至沒有回答你自己的問題...... – Spencer 2018-03-01 23:35:16

回答

92

香草薩特也非常漂亮解釋它here

準則#2:希望使虛擬功能保密。

這可以讓派生類重寫函數以根據需要自定義行爲,而不需要通過派生類使其可調用直接暴露虛函數(如果函數剛剛受保護,這將是可能的)。關鍵是存在允許定製的虛擬功能;除非它們也需要直接從派生類的代碼中調用,否則不需要使其他任何東西都變成私有的

+0

正如你從我的回答中可以猜到的,我認爲薩特的準則#3更傾向於將準則#2推出窗外。 – Spencer 2016-11-09 14:52:56

54

如果該方法是虛擬的,則它可以被派生類重寫,即使它是私有的也是如此。當調用虛擬方法時,將調用被覆蓋的版本。

(反對以香草薩特通過Prasoon Saurav在他的回答中,C++ FAQ精簡版recommends against private virtuals報價,主要是因爲它經常混淆的人。)

+32

看來,C++ FAQ Lite已經改變了它的建議:「C++ FAQ以前推薦使用受保護的虛擬而不是私有虛擬,然而私有虛擬方法現在已經足夠普遍,新手的混淆不再是一個問題。_「 – 2012-04-01 05:37:32

+2

但是,專家的困惑依然是一個問題,我旁邊的四位C++專業人員都沒有意識到私有虛擬機的存在 – Newtonx 2017-04-20 22:13:31

2

我用他們允許派生類爲「填補空白」這是一個基礎類,不會給最終用戶帶來這樣的漏洞。例如,我有一個高度有狀態的對象派生自一個公共基礎,它只能實現整個狀態機的2/3(派生類提供剩餘的1/3,具體取決於模板參數,並且基礎不能成爲模板其他原因)。

爲了使許多公共API正常工作(我正在使用可變參數模板),我需要共同的基類,但是我不能讓這個對象出現在野外。更糟糕的是,如果我將狀態機中的隕石坑以純虛擬功能的形式存放在任何地方,但是處於「私人」狀態,我允許來自其子類之一的聰明或無知的用戶覆蓋用戶不應該觸摸的方法。所以,我把狀態機的「大腦」放在私有虛擬功能中。然後,基類的直接子代在它們的非虛擬覆蓋上填充空白,並且用戶可以安全地使用結果對象或創建他們自己的進一步派生類,而不用擔心弄亂狀態機。

至於你不應該公開虛擬方法的論點,我說BS。用戶可以像使用公共虛擬機一樣輕鬆地覆蓋私有虛擬機 - 畢竟他們正在定義新類。如果公衆不應該修改給定的API,請不要在公共可訪問的對象中進行虛擬化。

4

我在閱讀Scott Meyers的'Effective C++'時遇到了這個概念,第35項:考慮虛擬函數的替代方案。我想引用斯科特·邁耶斯爲其他可能感興趣的人。

它是模板方法模式的一部分,通過非虛擬接口方式:面向公衆的方法不是虛擬的;相反,它們包裝了私有的虛擬方法調用。然後,基類是之前和私有虛擬函數調用後運行邏輯:

public: 
    void NonVirtualCalc(...) 
    { 
    // Setup 
    PrivateVirtualCalcCall(...); 
    // Clean up 
    } 

我認爲這是一個非常有趣的設計模式,我敢肯定,你可以看到添加的控制是如何有用。

  • 爲什麼要使虛函數private?最好的原因是我們已經提供了一個面向方法public
  • 爲什麼不簡單地使它protected,以便我可以使用該方法的其他有趣的事情?我認爲它總是取決於你的設計以及你相信基類是否適合。我認爲派生類製造商應該專注於實現所需的邏輯;其他一切已經得到照顧。另外,還有封裝問題。

從C++的角度來看,重寫私有虛擬方法是完全合法的,即使你無法從你的類中調用它。這支持上述設計。

7

儘管所有的調用聲明一個虛擬的成員私人,參數根本不持水。通常,派生類的虛擬函數覆蓋必須調用基類版本。它不能如它宣佈private

class Base 
{ 
private: 

int m_data; 

virtual void cleanup() { /*do something*/ } 

protected: 
Base(int idata): m_data (idata) {} 

public: 

int data() const { return m_data; } 
void set_data (int ndata) { m_data = ndata; cleanup(); } 
}; 

class Derived: public Base 
{ 
private: 
void cleanup() override 
{ 
    // do other stuff 
    Base::cleanup(); // nope, can't do it 
} 
public: 
Derived (int idata): base(idata) {} 
}; 

聲明基類方法protected

然後,你必須採取醜陋的指示方式,通過評論該方法應該被覆蓋,但沒有調用。

class Base 
{ 
... 
protected: 
// chained virtual function! 
// call in your derived version but nowhere else. 
// Use set_data instead 
virtual void cleanup() { /* do something */ } 
... 

因此,草藥薩特的指導方針#3 ......但是馬已經離開了穀倉。

當你聲明的東西protected你隱含信任任何派生類的作家正確理解和使用受保護的內部,只是friend聲明意味着private成員更深的信任的方式。

違反該信任而獲得不良行爲的用戶(例如,通過不打擾閱讀您的文檔標記爲「無知」)的用戶只能責怪自己。

更新:我有一些反饋,聲稱你可以使用私有虛函數以這種方式「鏈接」虛函數實現。如果是這樣,我一定會喜歡看到它。

我使用的C++編譯器絕對不會讓派生類實現調用私有基類實現。

如果C++委員會放寬「私人」以允許這個特定的訪問,我會全部用於私人虛擬功能。就目前而言,我們仍然被建議在馬被盜後鎖上馬廄門。

+0

我發現你的參數是無效的,你作爲一個API的開發人員應該爭取一個接口, **硬**錯誤地使用,而不是爲自己的錯誤設置另一個開發人員,這樣做你想在你的例子中做什麼可以只使用私有虛擬方法。 – sigy 2017-01-18 09:18:34

+0

@sigy然後你只需要發佈一個答案顯示派生類函數如何調用私有基類函數 – Spencer 2017-01-18 14:11:53

+0

這不是我說的,但是你可以重構代碼以達到相同的效果,而不需要調用私有基類函數 – sigy 2017-01-18 14:14:28