206

If I declare a base class (or interface class) and specify a default value for one or more of its parameters, do the derived classes have to specify the same defaults and if not, which defaults will manifest in the derived classes?

Addendum: I'm also interested in how this may be handled across different compilers and any input on "recommended" practice in this scenario.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
Arnold Spence
  • 21,942
  • 7
  • 74
  • 67
  • 1
    This seems an easy thing to test. Have you tried it? – andand Aug 20 '10 at 18:11
  • 27
    I am in the process of trying it but I haven't found concrete information of how "defined" the behaviour would be so I will eventually find an answer for my specific compiler but that won't tell me if all compilers will do the same thing. I'm also interested in recommended practice. – Arnold Spence Aug 20 '10 at 18:16
  • 1
    The behavior is well defined, and I doubt you'll find a compiler that gets it wrong (well, maybe if you test gcc 1.x, or VC++ 1.0 or something like that). Recommended practice is against doing this. – Jerry Coffin Aug 20 '10 at 18:25

6 Answers6

262

Virtuals may have defaults. The defaults in the base class are not inherited by derived classes.

Which default is used -- ie, the base class' or a derived class' -- is determined by the static type used to make the call to the function. If you call through a base class object, pointer or reference, the default denoted in the base class is used. Conversely, if you call through a derived class object, pointer or reference the defaults denoted in the derived class are used. There is an example below the Standard quotation that demonstrates this.

Some compilers may do something different, but this is what the C++03 and C++11 Standards say:

8.3.6.10:

A virtual function call (10.3) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object. An overriding function in a derived class does not acquire default arguments from the function it overrides. Example:

struct A {
  virtual void f(int a = 7);
};
struct B : public A {
  void f(int a);
};
void m()
{
  B* pb = new B;
  A* pa = pb;
  pa->f(); //OK, calls pa->B::f(7)
  pb->f(); //error: wrong number of arguments for B::f()
}

Here is a sample program to demonstrate what defaults are picked up. I'm using structs here rather than classes simply for brevity -- class and struct are exactly the same in almost every way except default visibility.

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

using std::stringstream;
using std::string;
using std::cout;
using std::endl;

struct Base { virtual string Speak(int n = 42); };
struct Der : public Base { string Speak(int n = 84); };

string Base::Speak(int n) 
{ 
    stringstream ss;
    ss << "Base " << n;
    return ss.str();
}

string Der::Speak(int n)
{
    stringstream ss;
    ss << "Der " << n;
    return ss.str();
}

int main()
{
    Base b1;
    Der d1;

    Base *pb1 = &b1, *pb2 = &d1;
    Der *pd1 = &d1;
    cout << pb1->Speak() << "\n"    // Base 42
        << pb2->Speak() << "\n"     // Der 42
        << pd1->Speak() << "\n"     // Der 84
        << endl;
}

The output of this program (on MSVC10 and GCC 4.4) is:

Base 42
Der 42
Der 84
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • Thanks for the reference, that tells me the behaviour I can reasonably expect across compilers (I hope). – Arnold Spence Aug 20 '10 at 18:22
  • 2
    This is a correction to my previous summary: I will accept this answer for the reference and mention that the collective recommendation is that it is ok to have default parameters in virtual functions as long as they don't change default parameters previously specified in an ancestor class. – Arnold Spence Aug 23 '10 at 13:20
  • I am using gcc 4.8.1 and I don't get a compile error "wrong number of arguments"!!! Took me a day and a half to find the bug... – steffen Aug 27 '13 at 14:08
  • 3
    But is there any reason for that? Why is it determined by static type? – user1289 Jul 03 '15 at 15:06
  • 6
    Clang-tidy treats default parameters on virtual methods as something unwanted and issues a warning about that: https://github.com/llvm-mirror/clang-tools-extra/blob/master/clang-tidy/google/DefaultArgumentsCheck.cpp – Martin Pecka May 15 '17 at 11:48
53

This was the topic of one of Herb Sutter's early Guru of the Week posts.

The first thing he says on the subject is DON'T DO THAT.

In more detail, yes, you can specify different default parameters. They won't work the same way as the virtual functions. A virtual function is called on the dynamic type of the object, while the default parameter values are based on the static type.

Given

class A {
    virtual void foo(int i = 1) { cout << "A::foo" << i << endl; }
};
class B: public A {
    virtual void foo(int i = 2) { cout << "B::foo" << i << endl; }
};
void test() {
A a;
B b;
A* ap = &b;
a.foo();
b.foo();
ap->foo();
}

you should get A::foo1 B::foo2 B::foo1

Pavan Manjunath
  • 27,404
  • 12
  • 99
  • 125
David Thornley
  • 56,304
  • 9
  • 91
  • 158
  • 9
    Thanks. A "Don't do that" from Herb Sutter carries some weight. – Arnold Spence Aug 20 '10 at 18:21
  • 2
    @ArnoldSpence, in fact Herb Sutter goes beyond this recommendation. He believes an interface shouldn't contain virtual methods at all: http://www.gotw.ca/publications/mill18.htm. Once your methods are concrete and can't (shouldn't) be overridden, it's safe to give them default parameters. – Mark Ransom Sep 23 '13 at 15:41
  • 4
    I believe what he meant by "don't do *that*" was "don't change the default value of the default parameter" in overriding methods, not "don't specify default parameters in virtual methods" – Weipeng Oct 11 '18 at 21:40
13

This is a bad idea, because the default arguments you get will depend on the static type of the object, whereas the virtual function dispatched to will depend on the dynamic type.

That is to say, when you call a function with default arguments, the default arguments are substituted at compile time, regardless of whether the function is virtual or not.

@cppcoder offered the following example in his [closed] question:

struct A {
    virtual void display(int i = 5) { std::cout << "Base::" << i << "\n"; }
};
struct B : public A {
    virtual void display(int i = 9) override { std::cout << "Derived::" << i << "\n"; }
};

int main()
{
    A * a = new B();
    a->display();

    A* aa = new A();
    aa->display();

    B* bb = new B();
    bb->display();
}

Which produces the following output:

Derived::5
Base::5
Derived::9

With the aid of the explanation above, it is easy to see why. At compile time, the compiler substitutes the default arguments from the member functions of the static types of the pointers, making the main function equivalent to the following:

    A * a = new B();
    a->display(5);

    A* aa = new A();
    aa->display(5);

    B* bb = new B();
    bb->display(9);
Oktalist
  • 14,336
  • 3
  • 43
  • 63
10

As other answers have detailed, its bad idea. However since no one mentions simple and effective solution, here it is: Convert your parameters to struct and then you can have default values to struct members!

So instead of,

//bad idea
virtual method1(int x = 0, int y = 0, int z = 0)

do this,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
Shital Shah
  • 63,284
  • 17
  • 238
  • 185
6

As you can see from the other answers this is a complicated subject. Instead of trying to do this or understand what it does (if you have to ask now, the maintainer will have to ask or look it up a year from now).

Instead, create a public non-virtual function in the base class with default parameters. Then it calls a private or protected virtual function that has no default parameters and is overridden in child classes as needed. Then you don't have to worry about the particulars of how it would work and the code is very obvious.

Mark B
  • 95,107
  • 10
  • 109
  • 188
5

This is one that you can probably figure out reasonably well by testing (i.e., it's a sufficiently mainstream part of the language that most compilers almost certainly get it right and unless you see differences between compilers, their output can be considered pretty well authoritative).

#include <iostream>

struct base { 
    virtual void x(int a=0) { std::cout << a; }
    virtual ~base() {}
};

struct derived1 : base { 
    void x(int a) { std:: cout << a; }
};

struct derived2 : base { 
    void x(int a = 1) { std::cout << a; }
};

int main() { 
    base *b[3];
    b[0] = new base;
    b[1] = new derived1;
    b[2] = new derived2;

    for (int i=0; i<3; i++) {
        b[i]->x();
        delete b[i];
    }

    derived1 d;
    // d.x();       // won't compile.
    derived2 d2;
    d2.x();
    return 0;
}
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 4
    @GMan: [Carefully looking innocent] What leaks? :-) – Jerry Coffin Aug 20 '10 at 18:17
  • I think he's referring to the lack of a virtual destructor. But in this case it won't leak. – John Dibling Aug 20 '10 at 18:24
  • 1
    @Jerry, destructor has be virtual if you are deleting derived object though base class pointer. Otherwise base class destructor will be called for all of them. In this it is ok as there is no destructor. :-) – chappar Aug 20 '10 at 18:24
  • 2
    @John: Originally there were no deletes, which is what I was referring to. I totally ignored the lack of a virtual destructor. And... @chappar: No, it's not okay. It *must* have a virtual destructor to be deleted through a base class, or you get undefined behavior. (This code has undefined behavior.) It has nothing to do with what data or destructors the derived classes have. – GManNickG Aug 20 '10 at 18:31
  • @Chappar: The code originally didn't delete anything. Though it's mostly irrelevant to the question at hand, I've also added a virtual dtor to the base class -- with a trivial dtor, it rarely matters, but GMan is entirely correct that without it, the code has UB. – Jerry Coffin Aug 20 '10 at 18:35