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;

// ...

}

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг:

R.10.2 Виртуальные функции

Из книги автора

R.10.2 Виртуальные функции Если класс base содержит виртуальную (§R.7.1.2) функцию vf, а производный от него класс derived также содержит функцию vf того же типа, тогда вызов vf для объекта класса derived является обращением к derived::vf, даже если доступ к этой функции происходит через


39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными

Из книги автора

39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными РезюмеВ базовых классах с высокой стоимостью изменений (в частности, в библиотеках) лучше делать открытые функции невиртуальными. Виртуальные функции лучше делать закрытыми, или защищенными —


Правило 9: Никогда не вызывайте виртуальные функции в конструкторе или деструкторе

Из книги автора

Правило 9: Никогда не вызывайте виртуальные функции в конструкторе или деструкторе Начну с повторения: вы не должны вызывать виртуальные функции во время работы конструкторов или деструкторов, потому что эти вызовы будут делать не то, что вы думаете, и результатами их


Правило 37: Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию

Из книги автора

Правило 37: Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию Давайте с самого начала упростим обсуждение. Есть только два типа функций, которые можно наследовать: виртуальные и невиртуальные. Но переопределять наследуемые невиртуальные


Определение функции с аргументом: формальные аргументы

Из книги автора

Определение функции с аргументом: формальные аргументы      Определение нашей функции начинается с двух строк: space(number)int number;Первая строка информирует компилятор о том, что у функции space( ) имеется аргумент и что его имя number. Вторая строка - описание, указывающее


Вызов функции с аргументом: фактические аргументы

Из книги автора

Вызов функции с аргументом: фактические аргументы      Задача в данном случае состоит в том, чтобы присвоить некоторую величину формальному аргументу number. После того как эта переменная получит свое значение, программа сможет выполнить свою задачу. Мы присваиваем


9.4.4. Аргументы со значениями по умолчанию

Из книги автора

9.4.4. Аргументы со значениями по умолчанию Наличие аргументов со значениями по умолчанию способно расширить множество устоявших функций. Устоявшими являются функции, которые вызываются с данным списком фактических аргументов. Но такая функция может иметь больше


17.5. Виртуальные функции в базовом и производном классах

Из книги автора

17.5. Виртуальные функции в базовом и производном классах По умолчанию функции-члены класса не являются виртуальными. В подобных случаях при обращении вызывается функция, определенная в статическом типе объекта класса (или указателя, или ссылки на объект), для которого она


17.5.8. Виртуальные функции, конструкторы и деструкторы

Из книги автора

17.5.8. Виртуальные функции, конструкторы и деструкторы Как мы видели в разделе 17.4, для объекта производного класса сначала вызывается конструктор базового, а затем производного класса. Например, при таком определении объекта NameQueryNameQuery poet( "Orlen" );сначала будет вызван


19.2.4. Объекты-исключения и виртуальные функции

Из книги автора

19.2.4. Объекты-исключения и виртуальные функции Если сгенерированный объект-исключение имеет тип производного класса, а обрабатывается catch-обработчиком для базового, то этот обработчик не может использовать особенности производного класса. Например, к функции-члену value(),


Задание параметров функции по умолчанию

Из книги автора

Задание параметров функции по умолчанию Еще одна интересная возможность, которая появляется у вас после перехода от Си к Си++, позволяет при определении функций задавать некоторые ее параметры по умолчанию. Вызывая такую функцию, можно не указывать параметры, заданные по


1.18 Виртуальные Функции

Из книги автора

1.18 Виртуальные Функции Предположим, что мы пишем программу для изображения фигур на экране. Общие атрибуты фигуры представлены классом shape, а специальные атрибуты – специальными классами:class shape (* point center; color col; //... public: void move(point to) (* center=to; draw(); *) point where() (* return center; *) virtual void


7.2.8 Виртуальные Функции

Из книги автора

7.2.8 Виртуальные Функции Виртуальные функции преодолевают сложности решения с пмощью полей типа, позволяя программисту описывать в базовом классе функции, которые можно переопределять в любом проиводном классе. Компилятор и загрузчик обеспечивают правильное


8.5.4 Виртуальные Функции

Из книги автора

8.5.4 Виртуальные Функции Если базовый класс base содержит virtual (виртуальную) (#8.1) функцию vf, а производный класс derived также содержит функцию vf, то обе функции должны иметь один и тот же тип, и вызов vf для объекта класса derived вызывает derived::vf. Например:struct base (* virtual void vf (); void f (); *);class