2010-04-13 65 views
3

比方說,我在C++的類層次結構:從一個類層次結構參數多態函數

class Base; 
class Derived1 : public Base; 
class Derived2 : public Base; 


class ParamType; 
class DerivedParamType1 : public ParamType; 
class DerivedParamType2 : public ParamType; 

而且我希望有一個多態函數,func(ParamType),在Base定義採取DerivedParamType1類型的參數爲Derived1以及針對Derived2DerivedParamType2類型的參數。

如果可能的話,如何在沒有指針的情況下完成這項工作?

+0

我承認'指針'位有點麻煩。你期望多態只在使用指針時出現:/? – 2010-04-13 12:59:23

+0

我的意思是我寧願傳遞給函數的參數最好是一個對象或引用而不是指針。 – myahya 2010-04-13 14:47:57

回答

2

根據哪些類繼承它,你不能讓Base :: func採用不同的參數。你需要改變一些東西。

你可以讓他們都採取ParamType,並處理好與你喜歡的任何機制意想不到的參數(例如拋出一個異常或返回錯誤代碼,而不是無效):對類型

struct ParamType; 
struct Base { 
    void func(ParamType&); 
} 
struct Derived1 : Base {}; 
//... 

或模板參數他們應該採取:

struct ParamType; 
struct DerivedParamType1 : ParamType {}; 
struct DerivedParamType2 : ParamType {}; 

template<class ParamT> 
struct Base { 
    void func(ParamT&); 
}; 
struct Derived1 : Base<DerivedParamType1> {}; 
struct Derived2 : Base<DerivedParamType2> {}; 

隨着第二溶液,Derived1和Derived2的將不共享一個共同的基礎,並且不能多態使用。

+1

理論上,覆蓋函數需要一個兼容的簽名,而不是相同的。事實上,C++確實允許協變返回類型。但是,輸入參數是相反變體,而非常量指針和引用是不變的。 – 2010-04-13 22:19:46

+0

@Ben:我不確定你想指出我的答案。 – 2010-04-13 23:12:51

2

您正在尋找協變參數。這在C++中是不可能的,C++只支持協變返回類型。

這不是簡單的語言中的遺漏。你試圖做的事實際上並不合理。想象一下,例如,您可以定義以下設置:

class Decoder { virtual void decode(Stream*); }; 
class Base64Decoder { void decode(Base64Stream*); }; 
class GZipDecoder { void decode(GZipStream*); }; 
... 
Decoder* d = new Base64Decoder; 
d.decode(new GZipStream("file.gz")); 

由於Decoder::decode()接受任何Stream,最後一行是有效的代碼。如果允許虛擬功能規則,Base64Decoder::decode將通過GZipStream

+0

對於僅用於輸入的參數,逆變參數滿足LSP,儘管C++不允許它們。你絕對正確的說,即使在理論上,協變參數也是無效的(它們只能是輸出,而C++沒有這個概念)。 – 2010-04-13 22:21:50

1

這叫做double dispatch,雖然這必須用基類的指針來完成,這就是多態如何工作的。在C++中,雙派遣不直接支持,所以涉及一些工作。

2

這打破了多態性的目的;如果Base提供功能Base::func(const ParamType&),則該Derived1中的相同功能(或其覆蓋)需要接受const ParamType&。您可以提供專門針對const DerivedParamType1&func的超載。

最接近你要找的是提供這樣一個專門的過載,然後使Derived1::func(const ParamType&)私人。但是請注意,這會打破多態性。多態性的要點是,如果你可以調用基類型的函數,那麼你可以在任何繼承它的類上調用同樣的函數(使用相同的參數),這顯然不是這種情況。

0

由於stefaanv說,這可以用雙重分派,而實現與一些額外的管道:

#include <iostream> 

class Derived1; 
class Derived2; 

class ParamType 
{ 
public: 
    virtual void invertFunc (const Derived1& deriv) const = 0; 
    virtual void invertFunc (const Derived2& deriv) const = 0; 
}; 

class DerivedParamType1; 
class DerivedParamType2; 

class Base 
{ 
public: 
    virtual void func (const ParamType& param) const = 0; 

    virtual void func (const DerivedParamType1& param) const 
    { 
     throw std::runtime_error ("Can not accept DerivedParamType1"); 
    } 

    virtual void func (const DerivedParamType2& param) const 
    { 
     throw std::runtime_error ("Can not accept DerivedParamType2"); 
    } 
}; 

class Derived1 : public Base 
{ 
public: 
    void func (const ParamType& param) const 
    { 
     param.invertFunc (*this); 
    } 

    void func (const DerivedParamType1& param) const 
    { 
     std::cout << "Derived1::func (DerivedParamType1)" << std::endl; 
    } 
}; 

class Derived2 : public Base 
{ 
public: 
    void func (const ParamType& param) const 
    { 
     param.invertFunc (*this); 
    } 

    void func (const DerivedParamType2& param) const 
    { 
     std::cout << "Derived2::func (DerivedParamType2)" << std::endl; 
    } 
}; 

class DerivedParamType1 : public ParamType 
{ 
public: 
    void invertFunc (const Derived1& deriv) const 
    { 
     deriv.func (*this); 
    } 

    void invertFunc (const Derived2& deriv) const 
    { 
     deriv.func (*this); 
    } 
}; 

class DerivedParamType2 : public ParamType 
{ 
public: 
    void invertFunc (const Derived1& deriv) const 
    { 
     deriv.func (*this); 
    } 

    void invertFunc (const Derived2& deriv) const 
    { 
     deriv.func (*this); 
    } 
}; 


int main (int argc, char* argv[]) 
{ 
    ParamType* paramType = new DerivedParamType1; 
    Base* deriv = new Derived1; 

    deriv->func (*paramType); 

    return 0; 
} 

注意,實際上有3周跳(急件)這裏你問Base::func(ParamType)打電話Derived1::func(DerivedParamType1)。如果你很高興與兩種:

Base::func(ParamType)調用DerivedParamType1::func(Derived1)

ParamType::func(Base)電話Derived1::func(DerivedParamType1)

則可以消除跳躍之一。

1

如果這些(操作符類和參數類型)是不同的概念,那麼它們應該保持獨立。 reinterpret_cast或重寫的方法中的東西,但如果它們是正交的,那麼根據你的要求做它是沒有意義的。

如果它們實際上並沒有分開,那就明白一點,並廢除虛擬功能的整個概念,因爲這不是你想要的。你知道你擁有的對象的類型,並且你知道參數的類型,所以在這種情況下,沒有任何東西是「虛擬」的。

class Base 
{ 
public: 
    class ParamType { } 

    void DoSomething(const ParamType&); // called by derived classes as necessary 
}; 

class Derived1 : public Base 
{ 
public: 
    class DerivedParamType1 : public ParamType { } 

    void DoSomething(const DerivedParamType1&); 
}; 

class Derived2 : public Base 
{ 
public: 
    class DerivedParamType2 : public ParamType { } 

    void DoSomething(const DerivedParamType2&); 
};