Virtual function default parameters and overloadin

2019-05-15 23:59发布

问题:

This question refers to common problems discussed in these questions:

Can virtual functions have default parameters?

Virtual functions default parameters

Here is what currently happens in c++ with default parameters to virtual functions:

struct Base
{
    virtual void foo(int one = 1, int two = 2)
            { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{
    virtual void foo(int one = 3, int two = 4) 
        { Base::foo(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

   b->foo();
   d->foo();
   dp->foo();

   return 0;
}

output:

one: 1 two: 2
one: 1 two: 2
 derived!
one: 3 two: 4
 derived!

This is the behaviour I wish existed in c++ virtual function default parameters:

#include <iostream>

using namespace std;

struct Base
{
    virtual void foo () { foo(1, 2); }
    virtual void foo (int one) { foo(one, 2); }
    virtual void foo(int one, int two)
            { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{   
    virtual void foo() { foo(3, 4); }
    virtual void foo(int one, int two) 
        { Base::foo(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

   b->foo();
   d->foo();
   dp->foo();

   return 0;
}

output:

one: 1 two: 2
one: 3 two: 4
 derived!
one: 3 two: 4
 derived!

So, basically, if I want to override a default parameter in a parent class, I will just create a new foo with that number of arguments. Note that the derived overrides the no-arg condition, but not the one-arg condition. As a side note, my current project uses a purely virtual base class. I often have pointers of the base class type and of the derived class type. I want a call from either pointer to have the same result.

QUESTIONS:

I have read many questions related to this subject, but they all don't seem to give reasonable solutions. Most of the solutions would result in uglier code all throughout your project.

Some say "Don't use default parameters on virtual functions," but then I would need to put the default in every place I call the function. It seems it would be better to just put a comment that says, "also change base class" and "also change derived class" than to have to change everywhere the function is called.

Some say to only have the default parameters in the base class, but that means that any pointer to a derived object would need to be cast back to a base pointer before the defaults could be used. That makes a lot of code ugly as well.

Are there reasons I should avoid the above design?

回答1:

At some point in time future maintainers of your code will be baffled, confused, and/or perplexed if you change the default values depending on which static type they call foo on so I'm going to assume that's not your concern.

Given that your concern is someone changing the default in the parent and forgetting to update the child class that's easily solved with the non-virtual interface pattern:

#include <iostream>

using namespace std;

struct Base
{
    void foo(int one = 1, int two = 2) { foo_impl(one, two); }

protected:
    virtual void foo_impl(int one, int two)
    { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{   
protected:
    virtual void foo_impl(int one, int two)
    { Base::foo_impl(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

    b->foo();
    d->foo();
    dp->foo();

    return 0;
}


回答2:

I think the problem in "understanding what's going on" is that default values for function arguments are resolved at compile-time - this means that unless it's VERY obvious and simple, the compiler will not KNOW what class some pointer is pointing at, and won't give the right argument. In other words, the compiler will use the class of the pointer when determining the arguments for your function. There is absolutely no way you can work around this (other than "knowing which class you want to use", but then it's pretty meaningless to use virtual functions).

The solution depends on what you REALLY want to do, but an obvious answer is to NOT use default arguments. The other solution is, like you have, some sort of indirect function call, where the "no arguments" function is different from the one and two argument functions.

But it's perhaps also a good idea to wonder if your design is right in the first place. Maybe you need to find a different solution somewhere...



回答3:

First of all, you are not using default parameters in your example. Thus, the problem that usual default parameters have do not apply to your code. In your code, subclasses can override default parameters which will work as expected no matter if a Derived is used through a Base or a Derived pointer. Thus, I do not see a problem with your approach, it seems fine to me.