Листинг 13.1. Умеют ли лошади летать...
1: // Листинг 13.1. Умеют ли лошади летать...
2: // Фильтрация метода Fly() в классе Horse
3:
4: #include
5:
6: class Horse
7: {
8: public:
9: void Gallop(){ cout << "Galloping...\n"; }
10: virtual void Fly() { cout << "Horses can't fly.\n"; }
11: private:
12: int itsAge;
13: };
14:
15: class Pegasus : public Horse
16: {
17: public:
18: virtual void Fly() { cout << "I can fly! I can fly! I can fly!\n"; }
19: };
20:
21: const int NumberHorses = 5;
22: int main()
23: {
24: Horse* Ranch[NumberHorses];
25: Horse* pHorse;
26: int choice,i;
27: for (i=0; i
28: {
29: cout << "(1)Horse (2)Pegasus: ";
30: cin >> choice;
31: if (choice == 2)
32: pHorse = new Pegasus;
33: else
34: pHorse = new Horse;
35: Ranch[i] = pHorse;
36: }
37: cout << "\n";
38: for (i=0; i
39: {
40: Ranch[i]->Fly();
41: delete Ranch[i];
42: }
43: return 0;
44: }
Результат:
(1)Horse (2)Pegasus; 1
(1)Horse (2)Pegasus: 2
(1)Horse (2)Pegasus: 1
(1)Horse (2)Pegasus: 2
(1)Horse (2)Pegasus: 1
Horses can't fly.
I can fly! I can fly! I can fly!
Horses can't fly.
I can fly! I can fly! I can fly!
Horses can't fly.
Анализ:Безусловно, эта программа будет работать ценой добавления в класс Horse редко используемого метода Fly(). Это произошло в строке 10. Для объектов данного класса этот метод констатирует факт, что лошади летать не умеют. И только для объекта Pegasus метод замещается в строке 18 таким образом, что при вызове его объект заявляет, что умеет летать.
В строке 24 используется массив указателей на объекты класса Horse, с помощью которого метод Fly() вызывается для разных объектов класса. В зависимости от того, для какого из объектов в данный момент вызывается метод, программа выводит на экран разные сообщения.
Примечание: Показанный выше пример программы был значительно сокращен, чтобы выделить именно те моменты, которые сейчас рассматриваются. Так, для простоты программы из нее были удалены конструктор и виртуальные деструкторы.
Перенос метода вверх по иерархии классов
Очень часто для решения подобных проблем объявление метода переносят вверх по иерархическому списку классов, чтобы сделать его доступным большему числу производных классов. Но при этом есть угроза, что базовый класс уподобится кладовке, захламленной старыми вещами. Такой подход делает программу громоздкой и нарушает саму идею иерархии классов в C++, когда производные классы дополняют своими функциями небольшой набор общих функций базового класса.
Противоречие состоит в том, что при переносе функции из производных классов вверх по иерархии в базовый класс трудно сохранить уникальность интерфейсов производных классов. Так, можно предположить, что у наших двух классов Bird и Horse есть базовый класс Animal, в котором собраны функции, общие для всех производных классов, например функция питания — Eat(). Перенеся метод Fly() в базовый класс, придется позаботиться о том, чтобы этот метод вызывался только в некоторых производных классах.
Приведение указателя к типу производного класса
Продолжая держаться за одиночное наследование, эту проблему можно решить таким образом, что метод Fly() будет вызываться только в случае, если указатель в данный момент связан с объектом Pegasus. Для этого необходимо иметь возможность обратиться к указателю и определить, на какой объект он указывает в текущий момент. Такой подход известен как RTTI (Runtime Type Identification — определение типа при выполнении). Но возможность выполнения RTTI была добавлена только в последние версии компиляторов C++.
Если ваш компилятор не поддерживает RTTI, можете реализовать его собственными силами, добавив в программу метод, который возвращает перечисление типов каждого класса. Возвращенное значение можно анализировать во время выполнения программы и допускать вызов метода Fly() только в том случае, если возвращается значение Pegasus.
Примечание: Не злоупотребляйте использованием RTTI в своих программах, так как этот подход рассматривается как аварийный и свидетельствует о том, что структура программы изначально была плохо продумана. Профессиональный программист предпочтет использование виртуальных функций, шаблонов или множественного наследования, речь о котором пойдет ниже в этой главе.
Чтобы вызвать метод Fly(), необходимо во время выполнения изменить тип указателя, определив, что он связан не с объектом Horse, а с объектом производного класса Pegasus. Этот способ называют приведением вниз, поскольку объект базового класса Horse приводится к объекту производного класса Pegasus.
Этот подход, хоть и с неохотой, теперь уже официально признан в C++, и для его реализации добавлен новый оператор — dynamic_cast.
Читать дальше