Haui's bytes

news, diary, journal, whatever

Default parameters

Recently I came across an interesting snippet of C++ code:

#include <iostream>
class Base {
    public:
    virtual void message1(){ std::cout << "Base message1" << std::endl; }
    virtual void message2(std::string param = "Base message2"){ std::cout << param << std::endl; }
};
class Derived : public Base {
    public:
    virtual void message1(){ std::cout << "Derived message1" << std::endl; }
    virtual void message2(std::string param = "Derived message2"){ std::cout << param << std::endl; }
};
int main(){
    Derived d;
    Base *base = &d;
    base->message1();
    base->message2();
    return 0;
}

If you compile this with g++ and run the produced binary you’ll get the following output:

Derived message1
Base message2

At first glance this might look a little confusing. It seems like the correct overloaded function is only called for message1 but not for mesage2. However, if you change the function bodies as follows, you can see that the correct function is called both times:

class Base {
    public:
    virtual void message1(){ std::cout << "Base::message1 Base message1" << std::endl; }
    virtual void message2(std::string param = "Base message2"){ std::cout << "Base::message2 " << param << std::endl; }
};
class Derived : public Base {
    public:
    virtual void message1(){ std::cout << "Derived::message1 Derived message1" << std::endl; }
    virtual void message2(std::string param = "Derived message2"){ std::cout << "Derived::message2 "<< param << std::endl; }
};

Derived::message1 Derived message1
Derived::message2 Base message2

As you can see, the correct overloaded functions of the derived class are called in both cases. The main problem stems from the default parameter for message2. Default parameters for C++ functions are resolved at compile time depending on the static type (here: Base), whereas the correct function is determined from the dynamic type (here: Derived). As a rule of thumb, you should never change the values for default parameters in inherited functions. Although it’s legal and the result is well defined, doing so will only lead to confusion and subtle errors. Another approach to avoid this issue is to abstain from using default parameters for virtual functions at all. If you want to know more about this an many other quirks of C++ you should take a look at Scott Meyer’s book Effective C++.