2011-10-28 106 views
10

在類層次結構的設計中,我使用抽象基類來聲明派生類將實現的各種方法。從某種意義上說,基類與C++中的接口非常接近。但是,有一個具體問題。考慮下面這宣告我們的接口類的代碼:在基類中返回抽象類型

class Interface { 
public: 
    virtual Interface method() = 0; 
}; 

class Implementation : public Interface { 
public: 
    virtual Implementation method() { /* ... */ } 
}; 

當然,這不會編譯,因爲你不能用C返回一個抽象類++。爲了解決這個問題,我用以下解決方案:

template <class T> 
class Interface { 
public: 
    virtual T method() = 0; 
}; 

class Implementation : public Interface<Implementation> { 
public: 
    virtual Implementation method() { /* ... */ } 
}; 

此解決方案是所有罰款和花花公子,但是,對我來說,它看起來並不很優雅,因爲文字的冗餘位這將是接口的參數。如果你們可以用我們的設計指出其他技術問題,我會很高興,但這是我唯一關心的問題。

有什麼辦法擺脫冗餘模板參數?可能使用宏?

注意:有問題的方法必須返回一個實例。我知道如果method()返回一個指針或引用,那就沒有問題了。

+1

您正在使用的習語稱爲[好奇地重複出現的模板圖案](http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Curiously_Recurring_Template_Pattern)。我想你可以用像'#define DERIVE_TEMPLATE_BASE(Derived,Base)class Derived:public Base '這樣的宏替換類聲明,但它看起來非常難看,並且可能會讓你的編輯器感到困惑。底線 - 是的,有一個冗餘,但它是完善和可識別的習慣用法。 – gwiazdorrr

+0

@gwiazdorrr:這看起來不錯。如果這是一個公認的習慣用法,那麼我可以假設它對於「界面」的用戶來說不會太陌生,對嗎? – Zeenobit

+1

另外需要注意的是(至少在本例中),將呼叫設爲虛擬是沒有意義的,因爲CRTP需要知道使用基本類型的最多派生類型。 –

回答

5

Interface::method()無法使用指針或引用返回Interface實例。返回一個非指針,非參考Interface實例需要實例化一個Interface本身的實例,這是非法的,因爲Interface是抽象的。如果你想在基類返回一個對象實例,你必須使用下列之一:

指針:

class Interface 
{ 
public: 
    virtual Interface* method() = 0; 
}; 

class Implementation : public Interface 
{ 
public: 
    virtual Interface* method() { /* ... */ } 
}; 

參考:

class Interface 
{ 
public: 
    virtual Interface& method() = 0; 
}; 

class Implementation : public Interface 
{ 
public: 
    virtual Interface& method() { /* ... */ } 
}; 

模板參數:

template<type T> 
class Interface 
{ 
public: 
    virtual T method() = 0; 
}; 

class Implementation : public Interface<Implementation> 
{ 
public: 
    virtual Implementation method() { /* ... */ } 
}; 
5

雖然你不能通過返回值對於顯而易見的r easons,它完全確定返回指針或引用 - 這被稱爲「協變返回類型」,它是虛函數重載的有效形式:

struct Base { virtual Base * foo(); } 
struct Derived : Base { virtual Derived * foo(); } 

的一點是,Derived::foo()是一個真正的覆蓋 ,而不是基地隱藏超載,因爲Derived*是一個指向派生類Base的指針。這同樣適用於參考。

換句話說,如果你有一個Base * p,你叫p->foo(),你總是可以把結果作爲一個指針Base(但如果你有更多的信息,比如你的類其實Derived,那麼你可以使用該信息)。

作爲C++的一部分,不允許使用相反的組合順序,即「逆變參數類型」。