|
Программирование >> Разработка устойчивых систем
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, работающий на основе подсчета ссылок. Класс содержит статический файло-
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |