Освой самостоятельно С++ за 21 день.
Шрифт:
Примечание:Во время компиляции неизвестно, объект какого класса захочет создать пользователь и какой именно вариант метода Speak будет использоваться. Указатель ptr связывается со своим объектом только во время выполнения программы. Такое связывание указателя с объектом называется динамическим, в отличие от статического связывания, происходящего во время компиляции программы.
Как работают виртуальные методы
При
Рис. 11.2. Созданный объект класса Dog
Рис. 11.3. Таблица виртуальных функций класса Mammal
Если в каком-то из объектов создается обычная не виртуальная функция, то всю полноту ответственности за эту функцию берет на себя объект. Большинство компиляторов создают таблицы виртуальных функций, называемые также v-таблицами. Такие таблицы создаются для каждого типа данных, и каждый объект любого класса содержит указатель на таблицу виртуальных функций (vptr, или v-указатель).
Хотя детали реализации выполнения виртуальных функций меняются в разных компиляторах, сами виртуальные функции будут работать совершенно одинаково, независимо от компилятора.
Рис. 11.4. Таблица виртуальных функций класса Dog
Итак, в каждом объекте есть указатель vptr, который ссылается на таблицу виртуальных функций, содержащую, в свою очередь, указатели на все виртуальные функции. (Более подробно указатели на функции рассматриваются на занятии 14.) Указатель vptr для объекта класса Dog инициализируется при создании части объекта, принадлежащей базовому классу Mammal, как показано на рис. 11.3.
После вызова конструктора класса Dog указатель vptr настраивается таким образом, чтобы указывать на замещенный вариант виртуальной функции (если такой есть), существующий для класса Dog (рис. 11.4).
В результате при использовании указателя на класс Mammal указатель vptr по- прежнему ссылается на тот вариант виртуальной функции, который соответствует реальному типу объекта. Поэтому при обращении к методу Speak в предыдущем
Нельзя брать там, находясь здесь
Если для объекта класса Dog объявлен метод WagTail, который не принадлежит классу Mammal, то невозможно получить доступ к этому методу, используя указатель класса Mammal (если только этот указатель не будет явно преобразован в указатель класса Dog). Поскольку функция WagTail не является виртуальной и не принадлежит классу Mammal, то доступ к ней можно получить только из объекта класса Dog или с помощью указателя этого класса.
Поскольку любые преобразования чреваты ошибками, создатели C++ допустили только явные преобразования типов. Всегда можно преобразовать любой указатель класса Mammal в указатель класса Dog, но есть более надежный и безопасный способ вызова метода WagTail. Чтобы разобраться в тонкостях упомянутого метода, необходимо освоить множественное наследование, о котором речь пойдет на следующем занятии, или научиться работе с шаблонами, что будет темой занятия 20.
Дробление объекта
Следует обратить внимание, что вся магия виртуальных функций проявляется только при обращении к ним с помощью указателей и ссылок. Если передать объект как значение, то виртуальную функцию вызвать не удастся. Эта проблема показана в листинге 11.10.
Листинг 11.10. Дробление объекта при передаче его как значения
1: //Листинг 11.10. Дробление объекта при передачи его как значения
2:
3: #include <iostream.h>
4:
5: class Mammal
6: {
7: public:
8: Mammal:itsAge(1) { }
9: virtual ~Mammal { }
10: virtual void Speak const { cout << "Mammal speak!\n"; }
11: protected:
12: int itsAge;
13: };
14:
15: class Dog : public Mammal
16: {
17: public:
18: void Speakconst { cout << "Woof!\n"; }
19: };
20:
21: class Cat : public Mammal
22: {
23: public:
24: void Speakconst { cout << "Meow!\ri"; >
25: };
26:
27: void ValueFunction (Mammal);
28: void PtrFunction (Mammal*);
29: void RefFunction (Mammal&);
30: int main
31: {
32: Mammal* ptr=0;
33: int choice;
34: while (1)
35: {
36: bool fQuit = false;
37: cout << "(1)dog (2)cat (0)Quit: ";
38: cin >> choice;
39: switch (choice)
40: {
41: case 0: fQuit = true;
42: break;
43: case 1: ptr = new Dog;
44: break;
45: case 2: ptr = new Cat;
46: break;
47: default: ptr = new Mammal;
48: break;
49: }
50: if (fQuit)
51: break;
52: PtrFunction(ptr);
53: RefFunction(*ptr);
54: ValueFunction(*ptr);
55: }
56: return 0;