Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 98
Текст из файла (страница 98)
Если находится узел, содержащий Ье11о, то вызов Ял4! "Ье(1о" ! возвращает указатель на этот узел. Для индикации того факта„ что никакой узел не найден, используйте исключение. 14.12. Упражнения 4?1 4. ('3) Определите класс 1яб который ведет себя как встроенный тип ?вб только он еше генерирует исключения в случаях переполнения (или потери точности). 5.
('2.5) Возьмите базовые операции открытия, закрытия, чтения и записи из стандартного интерфейса языка С на вашей системе и напишите эквивалентные функции на С++, которые вызывают функции языка С, но в случае возникновения ошибок генерируют исключения. 6. (*2.5) Напишите полный шаблон Уестог с исключениями типа Мавйе и Яке. 7. (*1) Напишите цикл, который суммирует объекты типа Уесгог из предыдущего упражнения без проверки размера векторов.
Почему это плохая идея? 8. ('2.5) Рассмотрите использование класса Ексерйоя как корневого для всей иерархии исключений. Как это будет выглядеть? Как это нужно использовать? К чему это может привести? Какие недостатки могут вытекать из такого подхода? 9. (*1) Имеется т(таама() (/* ... */) Внесите сюда изменения, направленные на перехват всех исключений, при возникновении которых выдаются сообшения об ошибке и вызывается абогт() .
Подсказка: функция сад Доя С() из 514.9 не полностью обрабатывает все случаи. 10. ('2) Напишите класс или шаблон, подходящие для реализации обратных вызовов. 11. (*2.5) Напишите класс Хоск (блокировка) для некоторой системы с параллельным выполнением. Иерархии классов Абстракция — это выборочное невежество.
— Эндрю Кениг Множественное наследование — разрешение неоднозначности — наследование и ив(лб-обьявяение — повторяющиеся базовые классы — виртуальные базовые классы — использование множественного наследования — управление доступом — защищенные члены — доступ к базовым классам — механизм ВТТ1— операция г(унит(с саве — статическое и динамическое приведения типов — приведение из виртуального базового класса — операция (уреЫ вЂ” расширенная информация о типе — правильное и неправильное применение КТТ1 — указатели на члены классов — свободная память — «виртуальные конструкторы» — советы — упражнения. 15.1.
Введение и обзор В этой главе обсуждается, как производные классы и виртуальные функции взаимодействуют с другими средствами языка, такими как контроль доступа, поиск имен, управление свободной памятью, конструкторы, указатели и преобразования типов. Глава состоит из пяти разделов: ° в15.2 Множественное наследование. ° $15.3 Контроль доступа. ° 515,4 Механизм ВТТ1 (Кцп-Типе Туре 1п(оппаг!оп).
° 515.5 Указатели на члены классов. ° 515.6 Свободная память. В общем случае, класс создается из некоторой структуры базовых классов. Ввиду того, что исторически такая структура практически всегда была древовидной, ее называют классовой иерархией (с!авв л(егагс(чу). Мы пытаемся проектировать классы таким образом, чтобы у пользователей не было необходимости интересоваться, каким именно образом классы сформированы. В частности, механизм виртуальных функ- Глава 15. Иерархии классов 474 ций гарантирует, что когда мы вызываем виртуальную функциюу() для некоторого объекта, вызывается всегда одна и та же функция в независимости от того, какой именно класс в иерархии объявил ее. В данной главе наибольшее внимание уделяется способам композиции классовых иерархий и контролю за доступом к разным частям классов, а также средствам навигации по классовым иерархиям как на этапе компиляции, так и во время выполнения программы.
15.2. Множественное наследование Как показано в й2.5.4 и 512.3, класс может иметь более одного непосредственного базового класса, то есть два и более классов могут указываться после двоеточия в обьявлении класса. Рассмотрим задачу моделирования, в которой параллельно выполняющиеся задания представлены классом ТЫ~ а сбор данных и визуализация представлены классом Рир1ауед. Далее мы можем определить класс моделируемых сущностей, например класс Ба(е1111е (спутник): с!азз Ба!еШ!е:риы/с Таз!с, риЫ/с РЬр1ауед ( // ...
)! Использование более одного непосредственного базового класса-принято называть мнозчсественным наследованием (ти1(!р!е !пнег!!апсе). В противоположность этому наличие единственного непосредственного базового класса называют одиночным наследованием (з/пя(е !пйеп!апсе).
Кроме операций, определенных специально для ЯагеИге, мы можем использовать объединение операций классов Таей и Рвр1ауед. Например: // Рцр!ауед::г(гач О // Тазйст!е!аур // Ба(е!!ые::(генетик (О Аналогичным образом, функциям можно передавать Багеау!е вместо ожидаемых ими Таей или Рар!ауей. Например: еоЫ )ь!еЫ!аЫ(РЫр!ауед*) ! гоЫ зизрепд ( Таз3с' ) ь Реализация этого механизма предполагает несложные приемы от компилятора, которые обеспечивают видимость разных частей Ба!е11йе в функциях, ожидающих Тавн или Рщз!ауе(1, соответственно. еоЫ/(Баге!!1!еь з) з.дган (); з. де!ау (!0); з . (гапзтй ( ); ) еоЫ а (Баге!1!!е* р) ( я!яя!!яя!(р) ь зизрепд (р); ) // передать указатель на О!зр!ауед-часть Ба(е!!Пе // передать указатель на Таз(г-часть Ба(е!!!(е 475 15.2.
Множественное наследование Виртуальные функции работают как обычно. Например: сгазз Тизл ( // ... Ызеиа! гоЫ репсдпя () = О! )( с1азз РЬр(ауег) ( // ... Ыг!иа1 гоЫ агагг() = д! )' с!азз Ба!е1йе: риЫ!с Тазя, риыдс Р!зр1ауег( ( // ... гоЫ реард(); //замещаем Таз)г::рспг)!пд() гоЫ йгае (); // замещаем Р!зр!ауег!:Ыгаи В )' Это гарантирует, что функции Яа!е1Ше:: Игал ( ) и БагеИуе::релйля и будут вызваны для Яа!е!И!е, проинтерпретированного как ЭЬр1ауе!! и Таза, соответственно. В случае одиночного наследования у программиста будет более ограниченный выбор для реализации классов ЭЬр1ауей, Таза и Яа!е!И!е. КагеИ1!е в таком случае может быть либо Таз!з, либо ЭЬр1ауеИ, но не обоими сразу (если только Таз!с не был реализован как производный класс от Р!зр1ауе!1, или наоборот).
Выбор любой из этих альтернатив означает потерю гибкости. А кому вообще может потребоваться такой вот класс Яа!еИ!!е? На самом деле, это вполне реальный пример (даже если кому-то он показался абсолютно искусственным). Существовала (а может и сейчас еще существует) программа, построенная по схеме, примененной здесь для иллюстрации множественного наследования. Она использовалась для исследования построения коммуникационных систем, включающих спутники (за!еИ!!ез), наземные станции и т.д.
В рамках этой модели можно отвечать на вопросы об интенсивности графика, находить правильную реакцию на ситуацию выведения из строя наземной станции (например, из-за торнадо), отыскивать оптимальное соотношение между нагрузкой на спутниковую связь и связь по Земле и т.д. Такое интенсивное моделирование предполагает множество операций вывода информации и множество отладочных операций. Также, нам нужно хранить состояние объектов Яа!еИ!!е и их подкомпонентов для анализа, отладки и восстановления после ошибок.
15.2.1. Разрешение неоднозначности Два базовых класса могут иметь функции-члены с совпадающими именами, Например: с(азз Тизл ( // ... иогиа1 йеЬид !л/о* де! йеЬид(); 476 Глава 15. Иерархии классов с!вяз РЬр1ауей ( // ... г!г!иа1 йеъид !й3о* ее! йеЬид () Для объектов типа Яа!еИИе эти неоднозначности должны устраняться: го!й Т(Яа!е!!1!е* зр) ( йеЬид (и!о* й!р=зр — >яе! йеЬид О / й!р = зр->Таза::ее! йеЬие () з сдр = зр->РЬр(ауей::ее! йеЬид() ! // еггог: неоднозначность // о/с // оФ Однако явное устранение неоднозначностей довольно неуклюже, так что обычно лучше определить новую функцию в производном классе: с1азз Яа!еййе: риЫ!с Таза, риЫ1с Р!зр(ауей ( / ... йебие !и!о* де! йеЬие() /замен(аеи Таз!нее! йеЬия() и Р/зр!ауейкяе! йеЬияГ) ( йеЬид !пТо* йр! = Таза::ее! йеЬие (); йеЬие !пуп* сдр2 = РЬр1ауей::ее! йебие(); ге!игп й!р1->текле (й!р2) ! ) Это локализует информацию о базовых классах для Яа!еИ!!е.
Так как Яа!ейге::Яе! йеЬиЯ() замещает функции Яе! йеЬил() для обоих базовых классов, Яаге!И- !е::Яе! йеЬезе () вызывается при каждом вызове для объекта Яа!еИуе. Квалифицированное имя Те!визг:: йгаю() может ссьшаться либо на функцию йаю(), объявленную в Те!з!аг, либо в одном из базовых классов. Например; с(азз Те(згаг: риЫ!с Яаге(рйе ( // ... гоЫйаю() ( йгаю() ! Яге1!1!е:: йгаю ( ); РЬр!ауейГ: й ан (); Ба!еИ1ге:: Р!зр!ауей:: йан ( ) ( ) ) //оорз(: рекурсивный вызов // походии Р/зр1ауейкйгаю /' избыточная двойная квалификация Другими словами, если Яа!еИИе::йгаю() не разрешается в одноименную функцию класса Яа!еИИе, то компилятор рекурсивно осуществляет поиск в базовых классах; то есть ищет Тазй:: йаю() и РЬр1ауей:: йаю() .
Если находится единственное соответствие, то оно и используется. В противном случае, Яа!еИИе:: йаю() либо не найдена, либо неоднозначна. 477 15,2. Множественное наследование 15.2.2. Наследование и ыв(лд-объявление Разрешение перегрузки не пересекает границ областей видимости различных классов (87.4).