При наследовании часто бывает необходимо, чтобы поведение некоторых методов базового класса и классов-наследников различалось. Решение на первый взгляд очевидное: переопределить соответствующие методы в производном классе.
Однако туг возникает одна проблема, которую лучше рассмотреть на простом примере (листинг 9.1).
Листинг 9.1. Необходимость виртуальных функций
II базовый класс
// метод базового класса
// предполагается
// вызов метода базового класса
#include <iostream> using namespace std; class Base { public:
int f(const int &d)
{ return 2*d; }
int CallFunction(const int &d) { return f(d)+l; }
// производный класс
// CallFunction наспедуется
// метод f переопределяется
};
class Derived: public Base { public:
int f(const int &d)
{ return d*d; }
};
// объект базового класса
// получаем 11
// объект производного класса
// какой метод f вызывается?
int main() { Base a;
cout << a.CallFunction(5)<< endl;
Derived b;
cout << b.CallFunction(5)<< endl; return 0;
}
В базовом классе определены два метода — f() и CallFunctionO, причем во втором методе вызывается первый. В классе-наследнике метод f () переопределен, а метод CallFunctionO унаследован. Очевидно, метод f () переопределяется для того, чтобы объекты базового класса и класса-наследника вели себя по-разному. Объявляя объект b типа Derived, программист, естественно, ожидает получить результат 5 5 + 1 = 26 — для этого и переопределялся метод f (). Однако на экран, как и для объекта а типа Base, выводится число 11, которое, очевидно, вычисляется как 2 5+1 = 11. Несмотря на переопределение метода f () в классе-наследнике, в унаследованной функции CallFunctionO вызывается «родная» функция f (), определенная в базовом классе!
Аналогичная проблема возникает и в несколько другом контексте: при подстановке ссылки или указателя на объект производного класса вместо ссылки или указателя на объект базового. Рассмотрим опять пример с часами и будильником (листинг 9.2).
Листинг 9.2. Неожиданная работа принципа подстановки
class Clock // базовый класс - часы
{ public:
void printO const { cout << "Clock!" << endl; }
};
class Alarm: public Clock // производный класс - будильник { public:
void printO const // переопределенный метод { cout << "Alarm!" << endl; }
Листинг 9.2 (продолжение)
void settime(Clock &d)
{d.printO; }
//.. .
Clock W;
settime(W);
Alarm U;
settime(U);
Clock *cl = &W;
cl->print();
cl = &U;
cl->print();'
// //
// // // // // // // //
функция установки времени
предполагается вызов метода базового класса
объект базового класса
выводится "Clock"
объект производного класса
ссылка на производный вместо базового
адрес объекта базового класса
вызов базового метода
адрес объекта производного типа вместо базового какой метод вызывается, базовый или производный?
Опять в классе-наследнике переопределен метод для того, чтобы обеспечить различное поведение объектов базового и производного классов. Однако и при передаче параметра по ссылке базового класса в функцию se 11 i me (), и при явном вызове метода print() через указатель базового класса наблюдается одна и та же картина: всегда вызывается метод базового класса, хотя намерения программиста состоят в том, чтобы вызвать метод производного. |