Пару слов о значениях по-умолчанию в производных классах

В этой заметке хотелось бы обратить внимание на одну проблему, с которой сталкиваются многие начинающие (и даже не очень) C++ программисты. Как видно из названия поста, проблема связана со значениями параметров по-умолчанию. «Что здесь сложного?» — спросите вы? Да вообщем-то ничего. Но, как оказалось, подводный камень таки есть, и желательно знать где он, если вы планируете и в дальнейшем программировать на этом языке.

Все мы знаем как работают виртуальные функции. В любом случае, в этой заметке я этого освещать не буду и если у вас с ними проблемы — закройте браузер и идите читать Страуструпа/Мейерса/Лафоре.

Итак, начнем. Рассмотрим следующий код:

#include <iostream>

// наш базовый класс
class Base
{
public:
    // обращаем внимание на значение: 42
    virtual void foo(int x = 42)
    {
        std::cout << "Base: " << x << std::endl;
    }
};

// наследник, перекрывающий виртуальный метод foo()
class Derived : public Base
{
public:
    // обращаем внимание на значение: 22
    virtual void foo(int x = 22)
    {
        std::cout << "Derived: " << x << std::endl;
    }
};

int main(int argc, char* argv[])
{
    Derived derived;

    Base* basePtr = &derived;
    Derived* derivedPtr = &derived;

    basePtr->foo();
    derivedPtr->foo();

    return 0;
}

Наверное, многие уже догадываются о чем пойдет речь. Но я все же поясню. Код очевиден: работа с объектом производного класса через указатели на базовый и производный классы. Как известно, виртуальность метода foo() говорит нам о том, что при вызове онного метода через указатель на базовый класс, вызовется реализация метода foo() из класса-потомка (Derived). Так оно и есть.. и все мы ожидаем увидеть следующие строки:

Derived: 22
Derived: 22

Но не тут-то было. На деле же мы увидим:

Derived: 42
Derived: 22

Реализация метода foo() вызовется из класса-потомка, а значение по умолчанию возьмется, все же, из базового класса. Вот такой вот он C++!

Идея поста: донести до читателей, что менять значение по-умолчанию в перекрываемых функциях — очень плохая затея. Это может привести к логической ошибке, обнаружение которой может оказаться не простой задачей.