开发者

Polymorphic functions with parameters from a class hierarchy

开发者 https://www.devze.com 2022-12-26 09:46 出处:网络
Let\'s say I have the following class hierarchy in C++: class Base; class Derived1 : public Base; class Derived2 : public Base;

Let's say I have the following class hierarchy in C++:

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


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

And I want a polymorphic function, func(ParamType), defined in Base to take a parameter of type DerivedParamType1 for Derived1 and a parameter of type DerivedParamType2 for Derived2.

How would this be done wi开发者_高级运维thout pointers, if possible?


You cannot have Base::func take different parameters depending on what class inherits it. You will need to change something.

You could make them both take a ParamType and handle an unexpected parameter with whatever mechanism you like (e.g. throw an exception or return an error code instead of void):

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

Or template on the type of parameter they should take:

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

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

With the second solution, Derived1 and Derived2 won't share a common base and cannot be used polymorphically.


That defeats the purpose of polymorphism; if Base provides a function Base::func(const ParamType&), then that same function (or an override of it) needs to accept const ParamType& in Derived1. You can provide an overload for func that is specialized on const DerivedParamType1&.

The closest thing to what you are looking for is to provide such a specialized overload, and then make Derived1::func(const ParamType&) private. Note, however, that this breaks polymorphism. The whole point of polymorphism is that if you can call the function on the base type, then you can call that same function (with the same parameters) on any class that inherits from it, which is clearly not the case.


You are looking for covariant parameters. This isn't possible in C++, which only supports covariant return types.

This isn't simply an omission in the language. What you are trying to do doesn't actually make sense. Imagine, for instance, you could define the following setup:

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"));

Since Decoder::decode() accepts any Stream, the last line is valid code. If virtual function rules allowed what you want, Base64Decoder::decode would be passed a GZipStream.


If these (operator class and parameter type) are separate concepts then they should remain separate. reinterpret_cast or something in your overridden methods, but if they're orthogonal then it doesn't make sense to do what you're asking.

If they aren't actually separate, make that obvious and do away with the whole notion of virtual functions, since that's not what you want. You know the type of object you have and you know the type of parameters, so in this case there's nothing 'virtual' about anything.

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&);
};


This is called double dispatch, although this will have to be done with pointers to the base class, that's how polymorphism works. In C++, double dispatch is not supported directly, so there is some work involved.


As stefaanv says, this can be achieved with double dispatch with some extra plumbing:

#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;
}

Note that there's actually 3 jumps (dispatches) here as you asked for Base::func(ParamType) to call Derived1::func(DerivedParamType1). If you are happy with either:

Base::func(ParamType) calls DerivedParamType1::func(Derived1)

or

ParamType::func(Base) calls Derived1::func(DerivedParamType1)

then you can eliminate one of the jumps.

0

精彩评论

暂无评论...
验证码 换一张
取 消