Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 98
Текст из файла (страница 98)
В этой главе особое внимание уделяется построению решеток классов, управлению доступом к частям классов и средствам навигации по решеткам классов во время компиляции и во время выполнения. Глава 15. Иерархии классон 444 15.2. Множественное наследование с1авв уа !е!!!!е: ри Ысе Така, ри Ы!с !г!вр!ауеа ( //- ); Использование более одного непосредственного базового класса обычно называется .инолсественны наследованием.
В противоположность атому наличие одного базового класса называется одиночггьси наследованием. Кроме операций, определенных для Яа!е11!!е, мы можем воспользоваться объединением операций Таза и О!вр(дуем. Например; оо! гЦ(уа!е!!!!ей з) ел!гав (), в.г!е!ау (10), в Ггапвт!г(); //!1!зр!ауег!кдгат О // Таз(г «де!ау () // баге!!г!ен! апзтй() Аналогичным образом, ба!еИ!!е можно передать в функции, которые ожидают Таза плц В!вр1ауег(.
Например: ио!да!у(ч!!уй! ~Жзр1ауед*~; ио!г!виврепг!(Тав!е'). ио!г! у (Ва!е!!!!е' р) ( д!уд!!уд! Ф, // передать указатель на Гнзр!ауед-чость ба Ге!у!е виврепг! (о) ' // передать указатель на Тазн-часть Ба!ерд!е Реализация этого механизма очевидно подразумевает (простые) методы компиля- ции для обеспечения топ ь что функции, ожидающие Тавн, у.видят часть Ва!е11!!е, от- личную от той, которую видят функции, ожидаюп(не 1»!вр1ауег(, Виртуальные функ- ции работают как обычно. Например: с1авв Таза ( //- и!ггиа! иоп! репг!!пу () 0; с(авз !г!вр!ауег( ( 0- пг!иа1 иоЫ г!гаги () = 0 ); с1аев 5 а!евве: риЫ!с Тазе, риЬ!!с сг!зр!а уег! ( //-. Как показано в ~ 2.5А и ~ 12.3, класс может иметь более одного непосредственного базового класса, то есть после в:» в объявлении класса может быть указано несколько классов.
Рассмотрим задачу моделирования, в которой параллельные задачи представ. лены классом Тав!с (задача), а сбор и вывод данных осуществляется при помои!и класса 1г!вр1ауес((отображаемый), Затем мы определим класс моделируемых сущностей, класс Яа!е111!е(спутник): 445 15.2.Множественное наследование О заиещенне Таздирепаупу() //занещенае Ргзр!ауейг:йгаш() оо!й репй!пу () оогййгаш (); )' 15.2.1. Разрешение неоднозначности Два базовых класса могут иметь функции-члены с одинаковым именем.
например: е1азз Тазд ( //- шггиа!йеЬиу !про*де! йеЬиу(); с!азз Р!ар!а уей (// ... иге!ив!йеЬиу !п)о'дег йеЬиа (); При использовании За1е(1!1е неоднозначности для этих функций должны быть уст- ранены: оо!йД5а1е!!!1е* зр) ( йеЬиу т~о* й!р = зр — >де1 йеЬид (); й!р= ар- Тавду! йеЬиу(); й!р = зр — >Рдзр!ауей:уе! йеЬиу (); //ошибка: неоднозначно // правильно // правильно Однако явное устранение неоднозначности довольно неудобно (слишком простран- но), поэтому.
лучше разрешить зти проблемы, определив новую функцию в производ- ном классе: Такой подход гарантирует, что функции Ба1е1!11есйгацг () и $а1е!!11есрепй(пд () будут вызваны для Яа(е!!!1е, проинтерпретированного как Р(зр!ауей и Таз)е соответственно. Обратите внимание, что прн наличии только одиночного наследования выбор реализации для классов Р!зр!ауей, Таза и Яа1е!!!1е у программиста будет ограниченным. Ва1е11!1е может быть Таей или Ргзр1ауей, но не обоими (если только Таз)е не является производным от Р(зр1ауей нли наоборот).
Выбор любой альтернативы ведет к потере гибкости. Кому может понадобиться класс За1е11!1е? Вопреки предположениям некоторых людей пример Ва(е!!11е является реальным. В действительности, существовала — и, может быть, существует до сих пор — программа, построенная способом, очень похожим на тот, что используется здесь для описания множественного наследования.
Она использовалась для изучения проектирования коммуникационных систем, включающих спутники, наземные станции и т. д. При наличии такой модели мы можем ответи~ь на вопросы об интенсивности трафика, определить правильные действия при блокировании одной из наземных станций (наприлгер, в результате ливня), правильно распределить трафик между спутниковой и наземной связью и т. д.
Подобное моделирование включает в себя множество отладочных функций и операций, отображающих ту или информацию. Кроме того, мы должны хранить состояние таких объектов как Яа(е(!!1е и их подкомпоиенты для анализа, отладки и восстановления после ошибок. Глава 15. Иерархии классов 446 с!аяк Ба1е!!!1е: риЬ!!с Тазд, ри616сР!яр!ауес!( //... //занегценпе Такуиууе! деЬиу() и0!кр!аней:уе! дебау!) с(ебид !п2о" уе! с/еЬиу () ( с!еЬид !и/о'йр! = Такд::уе! с(еЬиу(); деЬиу !пУо' йр2 - Р!яр!ауейуег с/ебиу (); ге1игп Фр!-хтегуе (йр2) с!акз Те!к1аг риЬ!!сои!е!!!1е( //...
ио!ь( Йгав () ( йив (); оа1е!!!1е:х!гав (), Рмр1ауей.й ав (); Басе!!!1ехР!кр!ауефх/гав (); // ошибка: рекурсивный вызов // находам О!кр!ауей:с!гои // избит~очная дводнпл квалификация ,Чругими словами, если Ва1е!!!1езг!гав [) не разрешается в ь!гав (), объявленную в ба1е!!!1е, коьшнлятор рекурснвно осуществляет поиск в базовых классах; то есть смотрит Таяйлс!гав () и Р!кр!ауеа::йав (). Если найдено ровно одно соотвстствие, используется найденное имя. В противном случае, функция За1е/(!1е: !!гав () либо не найдена, либо неоднозначна. 15.2.2. Наследование и ин(по-объявления Разрешение перегрузки не пересекает границ областей видимости классов Я 7А). В частности, неоднозначности между функциями нз различных базовых классов не разрешаются на основс типов аргумснтов.
При создании комбинации существенно различных классов, таких как Таей и Р!кра!уес( в примере За(е!!!1е, сходство имен обы гио пе означает сходство назначения. Подобные конфликты имен часто являются полной неожиданностью для програл1миста.
Например; с!акя Таяд ( //- ооЫ дебау (доиЬ!е р); //вывестли информацию люлько в мом си!!нае, // если приоритем ниже р с1аяя Р!кр!ауеа! ( Это локализует информацию о базовых классах Ба1е((!1е. Так как За1е!!!!езде! а!еЬид () замещает функции уе1 сгебиу() из обоих классов, Яа(е/(!1е::уе1 а!еЬиу() вызывается прн каждом вызове де! !!ебид() для объекта ба1е!(!1е. Квалифи!н1рованцое имя Те(к!ай:.дгав может ссылаться на с(гав, объявленную либо в Те(к!аг, либо в одном из его базовых классов.
Например: 447 15.2. Множественное наследование // чеи долыие о, теи болыие отладочной // инфориауии выводится иоЫ ь(едид (ии и); с!авв ВагеП!!е риЬПс Таей, риЫ!с Ргвр!ауед ( //... ); иоИ у (За!еППе* р) ( р — >Иедиу (1), р-> ТаеЬ; г!еди!! (1), р — >Ргвр!ауед: г!еЬи!1(!); ) Что если использование одного и того же имени в различных базовых классах яви- лось результатом тщательно продуманного проектного решения и пользователь хо- тел, чтобы выбор осуществлялся по типам аргумента? В этом случае, ив(аи-обьявле- ния (5 8.2.2) могут ввести обе функции в общуто область видимости, Например: с1аевА ( риЫих !п!/(!и ~; сдагДсдаг) //- с1авв В ( риЫ!с доиЫе/(г(оиЫе) //- // скрывает А и/!еда г) //Л:Я!а!) //АВн/!сдаг) ОВ: /(доиЫе) //АВ:ПЬАВ) Объявления иЫпд позволяют программисту создавать набор перегруженных функций из базовых и производных классов. Функции, объявленные в производном классе, скры- вают функции из базового класса, которые в противном случае были бы доступны.
Вир- туальные функции базового класса можно замещать как обычно Я 15.2.3.1), с!аввАВ риЬКсА, риЫьсВ ( риЫ!а ияауА:/ игйауВ /; сдпг/(сдал); АВ/64В), ооЫ д (АВй аЬ) ( аЬ|(1); аЬ|('а'); аЬ/(г,д); аЬДаЬ), // ошиоко: неоднозначность //Вохр!ауедндедиу!т!) или Тайндедид!доид!е)? // нравильно // нравильно 448 Глава 15. Иерархии классов изгпй-объявление (~ 8.2.2) в определении класса должно относиться к членам базового класса. из/пу-объявление нельзя использовать для члена класса вне этого класса, его производных классов или их функций-членов. из!пи-директив/ (з 8.2.3) нельзя поместить в определение класса, и она не может использоваться для класса.
из(пу-объявление не может использоваться для получения доступа к дополнительной информации. Оцо просто является механизмом предоставления более удобного доступа к информации, доступ к которой в принципе разрешен Я 15.3.2.2). 15.2.3. Повторяющиеся базовые классы При задании более чем одного базового класса возникает вероятность того, что какай-либо класс дважды окажется базовым для другого класса. Например, если бы каждый из классов Тпзй и О!зр1пуес( был производным от класса Еитй, у Зп1еП!1е было бы два Е!и/г: зинсг Е(пп ( Е!п)г' пехГ; с!азз Таей: риЫсс Ет)с ( //Е!пя используется для хранения списка всех задач Тазу (список планировщика) с!азз Осер!ауед ри!!сЕ!п)г ( // Егпя используется для хранения объектов всех отображаемьгх // (0(зр!ауед) объектов (список того, что отображается) Это не вызывает никаких проблем. Используются два отдельных объекта Е!и/г для представлениясвязей, изтидваспискансвзаимодействуютдругсдругом.
Естественно,обращаясь к членам класса Е!пй, вы рискуете получить неоднозначность 8 15,2.33 ). Объект Яа!еП!1е можно представить в графическом виде следующим образом; Е1~й Зи1еП11е В тех случаях, когда общий базовый класс не должен быть представлен в виде двух отдельных объектов, нужно воспользоваться виртуальным базовым классом Ц 15.2.4). Как правило, базовый класс, который повторяется (как Еспп в нагнем примере), является деталью реализации, которую не следует использовать вне непосредственно производных от него классов. Если к такому базовому классу нужен доступ нз места, где видна более чем одна копия базового класса, во избежание неоднозначности ссылка должна быть явно квалифицирована. Например: оо!д тезз т!1(г !!пкз (Баге!!(ге* р( р †>пел!= О; //оигибка: неоднозначно, какой !.тлу 449 15,2.множественное наследование '//о~иибкп: неоднозначно, кокай Е1пб? р — Егп!с пех1= Р; р — >Таха: пех1 = О, //правильно р — >Е>!вр1ауес(:.пехг = 0; //правильно //- Это в точности тот же механизм, который используется для разрешения неоднознач- ности при обращении к членам Я 15.2.1).
15.2.3.1. Замещение Виртуальная функция повторяющегося базового класса может быть замещена (единственной) функцией в производном классе. Например, можно следующим образом предоставить объекту возможность считывать себя из файла и записывать обратно в файл: с1аев О!огаб!е ( //храни.иый риЫ(с и!г1иа!сопвтсбаг'уе1 /1!е((=0; Ыг1и а1 иоЫ геай (( = О, Ыхша( иоЫ кгце (( = 0; и!г!па! -0!особ!е (( ( ( Естественно, несколько программистов могут воспользоваться этим для проектирования классов, которые могут применяться независимо илп в комбинации для построения более специализированных классов. Например, одним из способов завершения п возооновлеция моделирования является сохранение компонент моделирования с последующим их восстановлением.