ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 13.12.2020
Просмотров: 4309
Скачиваний: 28
Глава 17. Наследование классов
231
int main()
{
Derived d;
Base *b = &d;
cout << b->what() << endl; // печатает: 20
Base &c = d;
cout << c.what() << endl; // печатает: 20
return 0;
}
Повторять ключевое слово
virtual
в производном классе при за-
мещении функции базового класса не обязательно. Но лучше по-
вторить, чтобы не просматривать иерархию интерфейсов.
Функция, объявленная в базовом классе со спецификатором
virtual
, а также все функции, замещающие ее в производных
классах, называются
виртуальными
. Виртуальная функция не
может быть статическим членом класса.
Если виртуальные функции базового и производного классов от-
личаются только типом возвращаемого значения, то компилятор
выдаст ошибку. Например:
struct Base
{
virtual int what() { return 1; }
};
struct Derived: Base
{
virtual double what() { return 2.2; } // ошибка компиляции
};
Но при этом следует учитывать, что указатель и ссылка на базо-
вый класс совместимы по типам с указателем и ссылкой на про-
изводный класс соответственно. Поэтому виртуальная функция
производного класса может возвращать соответственно указатель
или ссылку на класс, который является производным от класса,
указатель или ссылку на который возвращает замещаемая вирту-
альная функция базового класса.
Если виртуальная функция производного класса имеет такое же
имя, как и виртуальная функция базового класса, но другую сиг-
Часть II. Язык программирования С++
232
натуру, то виртуальная функция в производном классе просто
скрывает соответствующую виртуальную функцию в базовом
классе.
В листинге 17.8 приведен пример сокрытия виртуальной функции
базового класса.
Листинг 17.8. Сокрытие виртуальной функции базового класса
#include <iostream>
using namespace std;
class Base
{
public:
virtual int what(int n) { return n + 10; }
};
class Derived: public Base
{
public:
virtual double what(double d) { return d + 20; }
};
int main()
{
Derived d;
Base *b = &d;
cout << b->what(5) << endl; // 15
cout << d.what(5.5) << endl; // 25.5
return 0;
}
Механизм замещения виртуальных функций можно обойти, если
при вызове виртуальной функции указать имя класса, которому
она принадлежит.
В листинге 17.9 приведен пример вызова виртуальной функции
базового класса через указатель на этот класс.
Глава 17. Наследование классов
233
Листинг 17.9. Обход механизма виртуальных функций
#include <iostream>
using namespace std;
class Base
{
public:
virtual int what() { return 10; }
};
class Derived: public Base
{
public:
virtual int what() { return 20; }
};
int main()
{
Base *b = new Derived;
cout << b->Base::what() << endl; // 10
return 0;
}
17.6. Полиморфизм
и позднее связывание
Возможность вызова виртуальных функций производных клас-
сов через указатель или ссылку на базовый класс называется
по-
лиморфизмом подтипов
(subtyping polymorphism), или
вклю-
чающим
(inclusion)
полиморфизмом
, или
просто
полиморфизмом
.
Класс, который содержит или наследует виртуальную функцию,
называется
полиморфным классом
.
Термин
позднее
,
или
отложенное
,
или
динамическое связывание
(dynamic binding) относится к тому обстоятельству, что компиля-
тор не может определить, какая виртуальная функция должна вы-
зываться, если к ней обращаются через указатель или ссылку на
базовый класс. Эта особенность виртуальных функций приводит
Часть II. Язык программирования С++
234
к тому, что адрес нужной виртуальной функции может быть оп-
ределен только во время исполнения программы. Позднее связы-
вание реализуется следующим образом:
для каждого полиморфного класса компилятор строит таблицу
адресов виртуальных функций (vtable);
каждый объект полиморфного класса содержит скрытый ука-
затель (vptr) на таблицу адресов виртуальных функций;
компилятор автоматически вставляет в начало конструктора
полиморфного класса фрагмент кода, который инициализиру-
ет vptr;
при вызове виртуальной функции ее адрес извлекается из таб-
лицы vtable, на которую указывает vptr объекта.
17.7. Передача
аргументов по умолчанию
в виртуальные функции
Механизм виртуальных функций не поддерживает передачу ар-
гументов по умолчанию в такие функции. То есть, если функция
производного класса вызывается через указатель или ссылку на
базовый класс, то ей передаются аргументы по умолчанию, ука-
занные в базовом классе. Это обусловлено тем, что аргументы по
умолчанию определяются на этапе компиляции.
В листинге 17.10 приведен пример передачи аргументов по умол-
чанию в виртуальную функцию.
Листинг 17.10. Передача аргументов по умолчанию
в виртуальную функцию
#include <iostream>
using namespace std;
class Base
{
public:
Глава 17. Наследование классов
235
virtual int what(int n = 10) { return n; }
};
class Derived: public Base
{
public:
virtual int what(int n = 20) { return n; }
};
int main()
{
Base* b = new Derived;
cout << b->what() << endl; // печатает: 10
return 0;
}
17.8. Виртуальные деструкторы
Так как объекты производных классов могут уничтожаться через
указатели на базовые классы при помощи оператора
delete
, то
деструкторы можно объявлять виртуальными в отличие от конст-
рукторов. Общее правило таково: если класс является полиморф-
ным, то его деструктор должен быть виртуальным. Причем этот
деструктор всегда должен иметь реализацию, т. к. он может быть
вызван из деструктора производного класса.
В листинге 17.11 приведен пример вызова виртуальных деструк-
торов.
Листинг 17.11. Вызов виртуальных деструкторов
#include <iostream>
using namespace std;
class Base
{
public:
virtual ~Base() { cout << "~Base" << endl; }
};