简体   繁体   中英

Polymorphic call to virtual functions with varying number of arguments

Is it possible to achieve behaviour demonstrated below with virtual functions?
And if it's not the correct way to go about polymorphism then what would be the correct way in this example?

class Base_
{
    float x;
    float y;
    float z;
public:
    Base_(float xx=0, float yy=0, float zz=0)
    {
        x = xx;
        y = yy;
        z = zz;
    }
   virtual void SetParemeters(what here?)=0; //Different number of arguments 
};

class Derived_1 :public Base_
{
    float r;
public:
    Derived_1(float rr=1, float xx=0, float yy=0, float zz=0):Base_(xx,yy,zz)
    {
        r=rr;
    }
    virtual void SetParemeters(float v1) //Different number of arguments
    {
        r=v1;
    }
};

class Derived_2 :public Base_
{
    float k;
    float w;
public:
    Derived_2(float kk=1, float ww=1,float xx=0, float yy=0, float zz=0):Base_(xx,yy,zz)
    {
        k=kk;
        w=ww;
    }
    virtual void SetParemeters(float v1, float v2) //Different number of arguments
    {
        k=v1;
        w=v2;
    }
};
int main()
{
    Derived_1 d1;
    Derived_2 d2;
    Base_ *ptr;

    ptr = &d1;
    ptr -> SetParemeters(one argument)

    ptr = &d2;
    ptr-> SetParemeters(one or two arguments) 

    return 0;
}

And even if I managed to achieve that, how can I set only second parameter (k) here:
ptr-> SetParemeters(one or two arguments) ?

I searched for answers but I only found answers to specific scenarios which made the whole thing difficult for me to understand.

Yes, make Base_::SetParameters takes two (optional) arguments:

class Base_
{
// [...]
public:
   virtual void SetParemeters(float=0f, float=0f)=0;
};

Derived_1::SetParameters just ignores the first one:

class Derived_1 :public Base_
{
    // [...]
    virtual void SetParemeters(float v1, float=0f)
    {
        r=v1;
    }
};

while Derived_2 takes the both of them

class Derived_2 :public Base_
{
    // [...]
    virtual void SetParemeters(float v1, float v2)
    {
        k=v1;
        w=v2;
    }
};

demo: https://coliru.stacked-crooked.com/a/c528ffff005df5b9

Note though, this significantly reduces the interest of virtual functions...

Derived_1 d1;
Derived_2 d2;
Base_ *ptr;

ptr = &d1;
ptr->SetParameters(one argument)

ptr = &d2;
ptr->SetParameters(one or two arguments) 

The way how this code is written implies that you have knowledge about two things:

  • at the time of the first call to SetParameters() , ptr points to an object of type Derived_1
  • at the second call, it points to an object of type Derived_2 .

This in turn means that you know the static types -- and in fact, you need to , due to the different signatures.

For such a scenario, dynamic polymorphism is not the right choice, because its premise is that you can talk to different implementations (overridden methods) using a uniform access (calling the virtual base method).

So, if you have this knowledge at compile time, simply use non-virtual method calls.


However, there are similar scenarios where you may actually be interested in supporting different signatures at runtime , for example if you load configuration dynamically. Here is an example, where not the number , but the types of arguments differ.

class Car : public Vehicle
{
    virtual void addFuel(const Petrol& f) override;
};

class Airplane : public Vehicle
{
    virtual void addFuel(const Kerosene& f) override;
};

How would then the base function look?

class Vehicle
{ 
    virtual ~Vehicle() {} // don't forget this!
    virtual void addFuel(const /* which type? */& f) = 0;
};

One option is to make the fuel type a hierarchy as well (both Kerosene and Petrol inherit the Fuel class):

class Vehicle
{ 
    virtual ~Vehicle() {}
    virtual void addFuel(const Fuel& f) = 0;
};

However, in this case, each implementation would need to either rely on the fact it's passed the right fuel, or check the type at runtime.

class Airplane : public Vehicle
{
    virtual void addFuel(const Fuel& f) override
    {
        if (auto* k = dynamic_cast<const Kerosene*>(&f))
        {
            // use concrete fuel
        }
    }
};

You can make SetParameters a variadic function and have the polymorphic interface be internal, providing the writeable parameters in a generic form (here as a vector of pointers to them):

class Base_
{
    float x;
    float y;
    float z;
public:
    Base_(float xx=0, float yy=0, float zz=0)
    {
        x = xx;
        y = yy;
        z = zz;
    }

    virtual std::vector<float*> getAllExtraParameters() = 0;

    template<class ... Ts>
    void SetExtraParameters(Ts&& ... ts)
    {
        auto extras = getAllExtraParameters();
        if (sizeof...(ts) > extras.size())
          throw std::runtime_error("Too many parameters given!");

        // Fold expression - could be implemented differently in C++ < 17.
        int index = 0;
        ((*extras[index++] = ts), ...);
    }
};


class Derived_1 :public Base_
{
    float r;
public:
    Derived_1(float rr=1, float xx=0, float yy=0, float zz=0):Base_(xx,yy,zz)
    {
        r=rr;
    }

    std::vector<float*> getAllExtraParameters() override
    {
        return { &r };
    }
};

class Derived_2 :public Base_
{
public:
    float k;
    float w;

    Derived_2(float kk=1, float ww=1,float xx=0, float yy=0, float zz=0):Base_(xx,yy,zz)
    {
        k=kk;
        w=ww;
    }

    std::vector<float*> getAllExtraParameters() override
    {
        return { &k, &w };
    }
};

Demo and tests: https://godbolt.org/z/ofXnuH

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM