2011-05-17 29 views
3

在C++中的一種方式,讓我們說我有派生類實現接口類BaseInterface,其中BaseInterface只有純虛函數和虛析構函數:++強制使用的界面,在c

class BaseInterface 
{ 
    public: 
    virtual void doSomething() = 0; 
    ~BaseInterface(){} 
}; 

class Derived : public BaseInterface 
{ 
    public: 
    Derived() {} 
    ~Derived(){} 

    protected: 
    virtual void doSomething(); 

    private: 
    int x; 
}; 

Derived類層次結構之外的任何類都不應該直接調用Derived :: doSomething(),即它只能通過BaseInterface類以多態方式訪問。爲了執行這條規則,我已經對Derived :: doSomething()進行了保護。這很好,但我正在尋找pro/con關於這種方法的意見。

謝謝!

+1

如果你的方法不是公共接口的一部分(不適用於課堂以外的用法),爲什麼要使它成爲'public'? – Nekuromento 2011-05-17 23:10:57

+0

@Nekuromento問題是,C++可以讓你做類Foo的事情:public Derived {public:virtual void doSomething(); } foo; foo.Derived :: doSomething的();這將繞過Foo的定義。 – Neil 2011-05-17 23:20:13

+2

記住你的BaseInterface析構函數也是虛擬的 – 2011-05-17 23:20:54

回答

8

我認爲你正在尋找非虛擬接口(NVI)模式:

class BaseInterface 
{ 
    public: 
    virtual ~BaseInterface(){} 
    void doSomething() { doSomethingImpl(); } 

protected: 
    virtual void doSomethingImpl() = 0; 
}; 

class Derived : public BaseInterface 
{ 
    public: 
    Derived() {} 
    virtual ~Derived(){} 

    protected: 
    virtual void doSomethingImpl(); 

    private: 
    int x; 
}; 
+0

抽象方法模式的用法不錯! – Suroot 2011-05-17 23:39:36

+0

我喜歡這種方法比我的原始代碼片段更好,因爲它更好地顯示了這個意圖,並且封閉了其他人在這個線程中指出的漏洞。謝謝大家! – KenK 2011-05-18 01:21:37

1

的關鍵:調用一個受保護或私人virtual實施public非虛接口是你的代碼的其餘部分。只接受BaseInterface *作爲任何需要doSomething()調用的方法的參數。客戶端程序員被迫從接口派生出來,以便編譯他的代碼。

+0

應用開放 - 封閉的原則,實現實例將在其他地方(可能是工廠)創建,並通過接口多態傳遞給客戶端 - 可能通過函數參數。所以這個想法是,客戶永遠不會知道實際實現在編譯時的情況。這也使代碼更容易單元測試。 – KenK 2011-05-19 02:44:40

3

如果它是界面的一部分,爲什麼不讓用戶調用它?請注意,實際上,他們可以稱之爲:static_cast<BaseInterface&>(o).doSomething()只是說o.doSomething()的一種尷尬方式。什麼是使用接口 ......如果對象滿足接口,那麼你應該能夠使用它,或者我錯過了什麼?

因爲你實際上並沒有阻止任何人調用方法,所以我沒有看到讓代碼更加複雜的一點(無論是類還是類的用戶)都沒有特別的理由。請注意,這與非虛擬接口完全不同,因爲在這種習慣用法中,虛擬功能無法公開(在任何級別上),而在您的情況下,其意圖是允許訪問並使其變得繁瑣。

+0

+1。而且我認爲改變派生類中方法的可見性並不是一個好的風格,因爲它會導致各種混淆。 – 2011-05-17 23:24:24

+0

我確實希望用戶調用該函數。我只是不希望他們被耦合到實現。它基本上是應用開閉原則。不過,我確實看到NVI模式如何使這個意圖更清晰。 – KenK 2011-05-19 02:41:32

0

這對我毫無意義。無論您撥打doSomething()的哪一個指針,您仍然可以使用大多數派生類中定義的方法。考慮以下情形:

class BaseInterface 
{ 
    public: 
    virtual void doSomething() = 0; 
    ~BaseInterface(){} 
}; 

class Derived : public BaseInterface 
{ 
    public: 
    Derived() {} 
    ~Derived(){} 

     virtual void doSomething(){} 

    private: 
    int x; 
}; 

class SecondDerived : public Derived 
{ 
    public: 
    SecondDerived() {} 
    ~SecondDerived(){} 

    private: 
    int x; 
}; 

int main(int argc, char* argv[]) 
{ 
    SecondDerived derived; 
    derived.doSomething(); //Derived::doSomething is called 

    BaseInterface* pInt = &derived; 
    pInt->doSomething(); //Derived::doSomething is called 

    return 0; 
} 
+0

這個想法會使Derived :: doSomething受到保護,所以如果層次結構之外的任何客戶端試圖調用它,它們會得到一個編譯錯誤。這可以讓編譯器強制執行該函數只能通過抽象接口使用的規則,因爲這是該函數公開的唯一地方。所以最終,編譯器強制客戶端與實現保持分離。 – KenK 2011-05-19 02:49:19

2

你在做什麼在標準ISO/IEC 14882中還提到:2003(E)11.6.1和相信你是正確的。除了這個事實之外,在給定的例子中,成員函數並不是純虛擬的。它也應該支持純虛函數,AFAIK。

虛擬函數的訪問規則(第11節)由其聲明決定,並且不受後面覆蓋它的函數規則的影響。

[Example: 

    class B 
    { 
    public: 
    virtual int f(); 
    }; 

    class D : public B 
    { 
    private: 
    int f(); 
    }; 

    void f() 
    { 
    D d; 
    B* pb = &d; 
    D* pd = &d; 

    pb->f(); // OK: B::f() is public, 
       // D::f() is invoked 

    pd->f(); // error: D::f() is private 
    } 

—end example] 

訪問被在調用點使用用於表示該成員函數被調用(在上面的例子中B *)的 對象的表達式的類型檢查。成員函數在被定義的類中的訪問(上例中的D)通常是未知的。