Программирование >>  Разработка устойчивых систем 

1 ... 146 147 148 [ 149 ] 150 151 152 ... 196


Pastel: Vendorl Vendor:

:v()

calling B(plp) Pastel::v() Vendorl::v() Vendor::f() delete mp -PastelO -VendorlO -VendorO -MyBaseО

Исходные библиотечные функции А() и В() работают так же, как раньше (предполагается, что новая версия v() вызывает свой прототип из базового класса). Деструктор стал виртуальным и работает так, как положено.

Хотя данная методика не отличается изяществом, она часто применяется на практике. В частности, она демонстрирует одно из предусловий множественного наследования: необходимость повышающего преобразования к обоим базовым классам.

Итоги

Одна из причин, по которым в С++ поддерживается множественное наследование, состоит в том, что С++ является гибридным языком и не может использовать единую монолитную иерархию классов, как Smalltalk или Java. Вместо этого С++ позволяет создавать множество самостоятельных деревьев наследования, поэтому иногда требуется объединить интерфейсы двух и более деревьев в новый класс.

А(р1р): Старое поведение cout calling B(plp)\n : B(plp): Старое поведение cout delete mp\n ; Удаление ссылки на объект в куче: delete 8imp: Правильное поведение } III:-

В классе MyBase (we использующем множественное наследование) и функция f(), и деструктор объявлены виртуальными, а в интерфейс добавлена новая виртуальная функция д(). Теперь мы должны заново создать каждый из производных классов исходной библиотеки, подключая к нему новый интерфейс средствами множественного наследования. Функциям Pastel::v() и Pastel::f() достаточно вызвать свои прототипы из базового класса. Но теперь при следующем повышающем преобразовании MyBase, как в main(), все вызовы через тр, включая вызов delete, становятся полиморфными:

MyBase* mp = pip: Повышающее преобразование

Кроме того, новая интерфейсная функция д() тоже может вызываться через тр.

Результат выполнения программы выглядит так:

calling f() Pastel::f() Vendorl::fО calling g() Pastel::g() calling A(plp)



Упражнения

1. Создайте базовый класс X с единственным конструктором, получающим аргумент int, и функцией f(), которая вызывается без аргументов и возвращает void. Теперь определите от X производные классы Y и Z, каждый из которых содержит конструктор с одним аргументом int. Создайте класс А, производный от Y и Z. Создайте объект класса А и вызовите f() для этого объекта. Решите проблему посредством уточнения.

2. Начните с результата упражнения 1. Создайте указатель на X с именем рх и присвойте ему адрес созданного ранее объекта типа А. Решите проблему, используя виртуальный базовый класс. Теперь исправьте X так, чтобы вам не приходилось вызывать конструктор X внутри А.

3. Начните с результата упражнения 2. Удалите уточнение вызова f() и посмотрите, удастся ли вызвать f() через рх. Посредством трассировки выясните, какая функция вызывается. Решите проблему так, чтобы в иерархии классов вызывалась правильная функция.

4. Создайте интерфейсный класс Animal с объявлением функции makeNoise(). Создайте интерфейсный класс SuperHero с объявлением функции savePer-sonFromFire(). Включите в оба интерфейсных класса объявление функции move() (не забудьте объявить методы интерфейсных классов чисто виртуальными). Определите три разных класса: SuperlativeMan, Amoeba и Тагап-tulaWoman. SuperlativeMan реализует интерфейс SuperHero, тогда как Amoeba и TarantulaWoman реализуют интерфейсы Animal и SuperHero. Определите две глобальные функции animalSouncl(Animal*) и saveFromFire(SuperHero*). Вызовите в обеих функциях все доступные методы интерфейсов.

Выражение принадлежит Заку Урлокеру (Zack Urlocker).

Если иерархия не содержит ромбов , множественное наследование обходится без особых сложностей (хотя вам все равно приходится решать проблему идентичных сигнатур функций в базовых классах). При появлении ромбовидных структур желательно избавиться от дублирования подобъектов за счет введения виртуальных базовых классов. Впрочем, это не только запутывает программу, но и усложняет базовую реализацию и снижает ее эффективность.

Множественное наследование называют командой goto 90-х годов . Сравнение весьма удачное: множественного наследования, как и команды goto, при нормальном программировании л5Д1ше избегать, но в отдельных случаях оно оказывается очень полезным. Оно принадлежит к числу второстепенных , но крайне нетривиальных возможностей C-i-i-, предназначенных для решения проблем, возникаюших в особых ситуациях. Если вы часто пользуетесь множественным наследованием, лишний раз проанализируйте свою мотивацию. Спросите себя: Нужно ли мне выполнять повышаюшее преобразование ко всем базовым классам? И если не нужно, вы основательно упростите себе жизнь, используя вложенные экземпляры всех классов, к которым повышающее преобразование выполнять не нужно.



5. Повторите предыдущее упражнение, но используйте для реализации интерфейсов щаблоны вместо наследования, как в примере Interfaces2.cpp.

6. Определите подключаемые классы, реализующие дополнительные возможности SuperHero (например, StopTrain, BendSteel, ClimbBuilding и т. д.). Переделайте упражнение 4 так, чтобы производные классы SuperHero наследовали от этих подключаемых классов и вызывали их функции.

7. Повторите предыдущее упражнение, преобразовав подключаемые классы в параметры шаблонов. Продемонстрируйте вызов этих функций.

8. Исключив интерфейс Animal из упражнения 4, переопределите класс Amoeba так, чтобы он реализовывал только SuperHero. Определите класс SuperlativeAmoeba, наследующий от SuperlativeMan и Amoeba. Попробуйте передать объект Amoeba функции saveFromFire(). Что нужно сделать, чтобы это стало возможным? Как виртуальное наследование влияет на размеры объектов?

9. В продолжение предыдущего упражнения добавьте целочисленную переменную strength Factor в класс SuperHero из упражнения 4, а также конструктор для инициализации этой переменной. Еще для ее инициализации включите конструкторы в три производных класса. Что необходимо сделать по-другому в SuperlativeAmoeba?

10. В продолжение предыдущего примера включите функцию eatFood() в SuperlativeMan и Amoeba (но не в SuperlativeAmoeba!), чтобы две версии eatFood() получали разные типы объектов, а их сигнатуры различались. Что нужно сделать в SuperlativeAmoeba для вызова любой из функций eatFood? Почему?

11. Определите для SuperlativeAmoeba операторы и =.

12. Удалите SuperlativeAmoeba из иерархии и измените класс Amoeba так, чтобы он был производным как от класса SuperlativeMan (который по-прежнему наследует от SuperHero), так и от класса SuperHero. Реализуйте виртуальную функцию workout() в SuperHero и SuperlativeMan() (с идентичными сигнатурами) и вызовите ее для объекта Amoeba. Какая функция будет вызвана?

13. Переопределите класс SuperlativeAmoeba так, чтобы его интерпретация в качестве SuperlativeMan или Amoeba осуществлялась посредством композиции вместо наследования. Обеспечьте выполнение неявного повыщения при помощи операторов преобразования. Сравните полученное решение с решением на базе наследования.

14. Допустим, вы получили предварительно откомпилированный класс Person (в вашем распоряжении только заголовочный файл и откомпилированный объектный файл). Пусть SuperHero содержит невиртуальную функцию work(). Сделайте так, чтобы класс SuperHero использовал версию work() класса Person. Для этого определите класс производным от Person и используйте реализацию Person::work(), но сделайте функцию SuperHero::work() виртуальной.

15. Определите подключаемый класс для регистрации ошибок ErrorLog, работающий на основе подсчета ссылок. Класс содержит статический файло-



1 ... 146 147 148 [ 149 ] 150 151 152 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика