17.5.4. Виртуальные функции и аргументы по умолчанию

17.5.4. Виртуальные функции и аргументы по умолчанию

Рассмотрим следующую простую иерархию классов:

#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 =

// вызывается base::foo( int )

// предполагалось, что будет возвращено 1024

pb-foo();

Кроме того, разработчик хотел, чтобы при вызове его реализации foo() без параметров использовался аргумент по умолчанию 2048:

derived d;

base *pb =

// вызывается 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.

Если реализации из производного класса при вызове через указатель или ссылку на базовый класс по умолчанию передается аргумент, указанный в базовом классе, то зачем задавать аргумент по умолчанию для реализации из производного класса?

Нам могут понадобиться различные аргументы по умолчанию в зависимости не от реализации foo() в конкретном производном классе, а от типа указателя или ссылки, через которые функция вызвана. Например, значения 1024 и 2048 - это размеры изображений. Когда нужно получить менее детальное изображение, вызываем foo() через класс base, а когда более детальное - через derived.

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

void

base::

foo( int ival = base_default_value )

{

int real_default_value = 1024; // настоящее значение по умолчанию

if ( ival == base_default_value )

ival = real_default_value;

// ...

}

Здесь base_default_value - значение, согласованное между всеми классами иерархии, которое явно говорит о том, что пользователь не передал никакого аргумента. Производный класс может быть реализован аналогично:

void

derived::

foo( int ival = base_default_value )

{

int real_default_value = 2048;

if ( ival == base_default_value )

ival = real_default_value;

// ...

}