2010-04-13 2 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 и параметр типа DerivedParamType2 для Derived2.

Как это можно сделать без указателей, если это возможно?

+0

Я признаю, что проблема с «указателями» немного. Вы ожидаете, что полиморфизм возникнет только при использовании указателей: /? –

+0

Я имел в виду, что я предпочел бы, чтобы параметры, переданные функции, предпочтительно были бы объектом или ссылкой, а не указателем. – myahya

ответ

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++ допускает ковариантные типы возврата. Однако входные параметры являются контравариантными, а указатели и ссылки, не являющиеся константами, являются инвариантными. –

+0

@Ben: Я не уверен, что вы пытаетесь указать на мой ответ. – 2010-04-13 23:12:51

2

Вы ищете ковариантные параметры. Это невозможно в 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++ не имеет этой концепции). –

1

Это называется double dispatch, хотя это должно быть сделано с указателями на базовый класс, так работает полиморфизм. В C++ двойная отправка не поддерживается напрямую, поэтому есть некоторая работа.

2

Это поражает цель полиморфизма; если Base предоставляет функцию Base::func(const ParamType&), то эта же функция (или ее переопределение) должна принимать const ParamType& в Derived1. Вы можете обеспечить перегрузку для func, которая специализируется на const DerivedParamType1&.

Ближайшая вещь к тому, что вы ищете, это обеспечить такую ​​специализированную перегрузку, а затем сделать 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&); 
}; 
Смежные вопросы