Рассмотрим следующую простую иерархию классов:
#include <iostream>
class base {
public:
virtual int foo( int ival = 1024 ) {
cout << "base::foo() -- ival: " << ival << endl;
return ival;
}
// ...
};
class derived : public base {
public:
virtual int foo( int ival = 2048 ) {
cout << "derived::foo() -- ival: " << ival << endl;
return ival;
}
// ...
};
Проектировщик класса хотел, чтобы при вызове без параметров реализации foo() из базового класса по умолчанию передавался аргумент 1024:
base b;
base *pb = &b;
// вызывается base::foo( int )
// предполагалось, что будет возвращено 1024
pb->foo();
Кроме того, разработчик хотел, чтобы при вызове его реализации foo() без параметров использовался аргумент по умолчанию 2048:
derived d;
base *pb = &d;
// вызывается derived::foo( int )
// предполагалось, что будет возвращено 2048
pb->foo();
Однако в C++ принята другая семантика механизма виртуализации. Вот небольшая программа для тестирования нашей иерархии классов:
int main()
{
derived *pd = new derived;
base *pb = pd;
int val = pb->foo();
cout << "main() : val через base: "
<< val << endl;
val = pd->foo();
cout << "main() : val через derived: "
<< val << endl;
}
После компиляции и запуска программа выводит следующую информацию:
derived::foo() -- ival: 1024
main() : val через base: 1024
derived::foo() -- ival: 2048
main() : val через derived: 2048
При обоих обращениях реализация foo() из производного класса вызывается корректно, поскольку фактически вызываемый экземпляр определяется во время выполнения на основе типа класса, адресуемого pd и pb. Но передаваемый foo() аргумент по умолчанию определяется не во время выполнения, а во время компиляции на основе типа объекта, через который вызывается функция. При вызове foo() через pb аргумент по умолчанию извлекается из объявления base::foo() и равен 1024. Если же foo() вызывается через pd, то аргумент по умолчанию извлекается из объявления derived::foo() и равен 2048.