- •Наследование.
- •Производный класс
- •В файле student2.H
- •В файле student2.H
- •В файле student2.H
- •В файле student2.Cpp
- •Повторное использование кода: класс двоичного дерева
- •В файле gentree2.H
- •В файле gentree2.Cpp
- •Виртуальные функции
- •В файле virt_sel.Cpp
- •В файле virt_err.Cpp
- •В файле shape2.Cpp
- •Virtual double area () const {return 0;} //площадь
- •Абстрактные базовые классы
- •В файле predator.Cpp
- •Шаблоны и наследование
- •Порядок выполнения конструкторов
- •Наследование и проектирование
- •Форма подтипов
- •Идентификация типа на этапе выполнения
- •В файле typied.Cpp
Виртуальные функции
Перегруженная функция-член вызывается с учетом алгоритма соответствия типов, в который входит правило соответствия неявного аргумента объекту данного типа класса. Все это известно на этапе компиляции и позволяет компилятору напрямую выбирать надлежащий член. Как станет очевидно, было бы не плохо динамически (на этапе выполнения) выбирать соответствующую функцию-член среди функций базового и производного классов. Ключевое слово служит спецификатором функции, и как раз и представляет подобный механизм, но оно может применяться для изменения объявления только функций-членов. Сочетание виртуальных функций и открытого наследования станет для нас наиболее обобщенным и гибким способом построения программного обеспечения. Это – форма чистого полиморфизма.
Обычная виртуальная функция должна представлять собой исполняемый код. При вызове семантика ее точно такая же, как и у остальных функций. В производном типе она может замещаться (переписываться, переопределяться), и прототип производной функции должен иметь сигнатуру и возвращаемый тип , совпадающие с базовой. Выбор того, какое определение функции вызвать для виртуальной функции, происходит динамически (на этапе выполнения). Типичный случай – это когда базовый класс включает виртуальную функцию, а производные классы имеют свои версии этой функции. Указатель на базовый класс может указывать либо на объект базового класса, либо на объект производного класса. Выбор функции-члена будет зависит от класса объекта, на который фактически (на момент выполнения) направлен указатель, а не от типа указателя. При отсутствии члена производного типа используется виртуальная функция базового класса. Обратите внимание на различие между выбором надлежащей замещенной виртуальной функции и выбором перегруженной функции-члена. Перегруженная функция-член выбирается на этапе компиляции на основе сигнатуры; перегруженные функции могут иметь различные возвращаемые типы. Виртуальная функция выбирается на этапе выполнения на основе типа объекта, который передается ей в качестве аргумента-указателя this. Кроме того, будучи однажды объявленной как виртуальная, функция сохраняет это свойство во всех переопределениях в производных классах. В производных классах не обязательно использовать модификатор virtual.
Рассмотрим следующий пример:
В файле virt_sel.Cpp
//Выбор виртуальной функции
class B {
public:
int i;
virtual void print_i() const
{ count << i << “ внутри B” << end1; }
};
class d : public:B
public^
//тоже виртуальная
void print_i() const
{ count << i<< “ внутри D” << end1; }
};
int main()
{
B b;
B* pb = &b; //указывает на объект В
Dd;
d.1 = 1 + (b.i = 1);
pb -> print_i(); //вызов В::print_i()
pb = &b; //указывает на объект D
pb -> print_i(); //вызов D::print_i()
}
Вот что выведет эта программа:
1 внутри В
2 внутри D
Сравните это поведение с программой student из раздела 1.2, «Преобразования типов и видимость», на стр. 284. Там выбор функции print() был основан на типе указателя, известном на этапе компиляции. Здесь же функция print_i() выбирается на основе того, на что направлен указатель. При этом выполняется другая версия print_i(). Говоря языком ООП, объекту посылается сообщение print_i(), и задействует собственную версию соответствующего метода. Так, тип базового указателя не определяет выбор метода (функции). Объекты другого класса обрабатываются другими функциями, устанавливаемыми на этапе выполнения. Средства, позволяющие реализовывать АТД, наследование, а также возможность динамической обработки объектов, являются неотъемлемой частью ООП.
Виртуальные функции и перегрузка функций–членов вызывают путаницу. Рассмотрим следующий пример.