Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 99
Текст из файла (страница 99)
В частности, неоднозначности между функциями из разных базовых классов не разрешаются на основе типов аргументов. При комбинировании существенно разных классов, таких как Тая!с и Э!яр1аует1 в примере с классом Хате!1!ге, сходство в названиях функций не означает сходства в их назначении. Такого рода конфликты имен часто становятся полным сюрпризом для программистов.
Например: с!а55 Тата ( // .. ю!И йебиа (т!оиЫе р); с1аее Р1ер1ауеИ ( //... юавйеЬид(!и! г) т ) с1аее Ваге!!!ге: риЫ!с Таза, риЬБс Р!ер!ауей ( // ... )' юЫ е (Ваге!!!1е* р) ( р-)йеви8 (1) г //еггог: неодиозиачно — Рйр!ауейлчуеЬих(!ит) или Таз)т;:т(еЬи81тгоиЫе)? р-ьТаеа;:ч!еаи8 (1); // о/с р-)Р!ер1ауеИ::деби8(1); //о/с А что, если совпадение имен функций в разных базовых классах было продуманным проектным решением, но пользователь хочет различать их по типам аргументов? В этом случае объявления ия(ил (88.2.2) могут ввести обе функции в общую область видимости. Например: с1аее А ( риЫ!с: (и! Т(1и!); саагТ(сааг); // ...
)) с1ате В ( риЫ1с: ФоиЫе Т(ч(оиЫе); 478 Глава 15. Иерархии классов с!вез АВ: риы(с А, риЫ!с В ( риЬВс: иипд А::)) из!пя В::) ! слег !(влас) ! АВ!'(АВ) ! // скрывает А:.)(сьак) соЫВ(АВъ аЬ) ( аЬ.((1) ! аь.!('а') ! аь.((г.о); аь.!(аь); // А:./(!и!) //АВ:./(влас) // В:. 1(доиые) //АВ: /(АВ) 15.2.3. Повторяющиеся базовые классы При наличии нескольких базовых классов становится возможной ситуация, когда какой-то класс дважды окажется базовым для другого класса. Например, если бы каждый из классов Тив(с и Ю1вр1ауиб был бы производным от класса Х!и!г, то класс Юа!е!11!в получал бы бы класс Игй в качестве базового дважды; з!кис! !Апь ( !лпь* пех(! )! с1азз Таль: риЫ!с 2лпь ( В Ь!и!с используется для списка задач типа Тазк (список планировщика) //... )) с(азв Вар!ауеи: риЬВс !.1пк //!.1п)( используется для списка отобраясаемьп (Р!зр!ауед) объектов (список показа) Объявления из!иб позволяют программисту сформировать набор перегруженных функций из базовых классов и производного класса.
Функции, объявленные в производном классе, скрывают в противном случае доступные функции из базовых классов. Виртуальные функции базовых классов могут замешаться как обычно ($1 5.2.3.1). Объявление из!ия (З8.2.2) в определении класса должно ссылаться на члены базового класса. Вне классов объявление из)ия не может ссылаться на члены класса, на его производные классы и их члены. л(ирективы ив!иВ(88 2 3) нельзя помещать в определение класса и их нельзя использовать дяя классов.
Невозможно использовать объявления изй(В для получения доступа к дополнительной информации. Это лишь способ упростить доступ к имеющейся информации (ф! 5.3.2.2). 15.2. Множественное наследование В принципе, это проблем не вызывает. Просто для представления связей используются два объекта типа Хай, которые не мешают друг другу. В то же время, обращаясь к членам класса Тли/с, вы рискуете нарваться на неоднозначности (515.2.3.1).
Объект класса оагеИ/ге можно изобразить графически следующим образом: Ыпй 1.!и/с Таза 1//зр/а ес/ Яа!еШ!е гоЫ тезз нздс 1тйз ьза!е!/!!е* р) ( р->иех! = 0; р->1./п/с с с пех! = О; У еггогс неоднозначность (какой (сп/с?) У еггогс неоднозначность (какой Ь!пкр) сс' о/с сУ о/с р->Таза с: пех! = О; р->/З!зр/ауес/с с иех! = 0; ьс ... ) Это в точности тот же механизм, что используется для разрешения неоднозначностей при доступе к членам (515.2.1). 15.2.3.1. Замещение Виртуальная функция реплицируемого базового класса может замешаться (единственной) функцией производного класса.
Например, можно следуклцим образом обеспечить объекту возможность читать свое состояние из файла и записывать его в файл: с/азз о!огаЫе ( риЬдс: г! !иа1 сопз! с/саг Ое! Яе () Иг!иа1 гоЫ геас/() = 0; Ыпиа1 гоЫ и п(е () = Ос к!ггиа/-5(огаЫе ( ) ( ) )' Естественно, разрабатывая свои собственные специализированные системы, разные программисты могут воспользоваться этим определением для проектирова- В случаях, когда базовый класс не должен быть представлен двумя раздельными объектами, нужно использовать так называемые виртуальные базовые классы (015.2с4).
Обычно, реплицируемый базовый класс (такой как Хий в нашем примере) должен быть деталью реализации и его не следует использовать вне непосредственно производных от него классов. Если к такому базовому классу нужен доступ из места, где видны его множественные копии, нужно явно квалифицировать ссылки во избежание неоднозначностей. Например: 4ВО Глава 15.
Иерархии классов ния классов, которые могут использоваться независимо друг от друга или в некоторой комбинации. Например, в задачах моделирования при остановке работы важно сохранять состояние объектов моделирования с последующим их восстановлением при рестарте. Это можно было бы реализовать следующим образом: с1алл Тгаплт!Пег: риЫ(с ЯгогаЫе ( риЫ(с: юЫ теле(); У...
)' с(ат ИесеЬег: риЫ(с агогаЫе ( риЫ1с: юЫ тг(ге (); (.' )1 с1алл НаЖо: риЫ(с Тгаплт(пег, риЫ1с Кесеыег ( риЫ(с: сопл сйаг* аег 111е(); юЫ ген (); юЫ тг11е (); ,ч' .. )1 Часто замешаюшая функция вызывает соответствующую версию базового класса, после чего выполняет специфическую именно для производного класса работу: гоЫ ЯаЖо:: илге () Тгаплт(Пег:: нггге ( ); Веселее:: шыге ( ) г У далее пишем информацию, специфичную дла йайо ) Преобразование от типа реплицируемого базового класса к производному классу обсуждается в З(5.4.2.
Техника замещения каждой из функций ит11е () отдельными функциями производных классов обсуждается в в25.6. 15.2.4. Виртуальные базовые классы Пример с классом Майо из предыдущего раздела работает потому, что класс ЯгогаЫе реплицируется вполне безопасно, удобно и эффективно. Однако это часто не свойственно классам, служащим строительными блоками для других классов.
Например, мы могли бы определить класс огогаЫе так, чтобы он содержал имя файла, используемого для хранения объектов: с1ат ЯгогаЫе ( риЫ(с: ЯгогаЫе(сопл( спаг* л) 481 ( 5.2 Множественное наследование Ыг!иа1 юЫ геая( ( ) = О; Ыгпяа! юЫ пт!яе(] = О; г!гтиа! -$(огаЫе () (ягг!ге (); ) ргыаге: сопя! сааг* йоге> $гогаЫе ( сопя! $яогаЫе ь ); $гогаЫеа орегагог= (сопя! $!огаЫеа ) Несмотря на внешне незначительное изменение класса $1огабуе, мы должны изменить дизайн класса !!ая(1о. Все части объекта должны совместно использовать единственную копию $гогаЫе; в противном случае становится неоправданно сложно избегать хранения множественных копий объекта. Одним из механизмов, обеспечивающих такое совместное использование, является механизм виртуального базоеого класса (т!ггиа! Ьаяе с!аяя).
Каждый виртуальный базовый класс вносит в объект производного класса единственный (разделяемый) подобъект. Например: с1еяя Тгапят!Пег: риЫЫ г!гяиа1 $(огаЫе ( риЫяс: юЫ юйе(); ~У... )' с(аяя Весе!юг: риЬБс Ыпиа! $!огаЫе ( риЫяс: юЫ пяйе() г гу... )' сйт Кайо: риЬдс Тгапят!иег, риЫ(с Кесеыег ( риЬбс: го!я( пт!!е ( ); гу... )' Или графически: Тгапят1пег Яайо Сравните эту диаграмму с графическим представлением объекта класса Хате!!!!е в 815.2.3, чтобы увидеть разницу между обычным и виртуальным наследованием. Любой заданный виртуальный базовый класс в иерархии наследования всегда пред- 4В~ Глава ) 5. Иерархии классов « ставим единственной копией своего объекта. В противоположность этому, невиртуальный базовый класс иерархии представляется своим собственным отдельным подобъектом. 15.2.4.1.
Программирование виртуальных базовых классов Определяя функции класса с виртуальным базовым классом, программист, в общем случае, не может знать, используют ли другие классы тот же самый виртуальный базовый класс. Это приводит к затруднениям, когда в рамках конкретной задачи требуется обеспечить единственный вызов функций базового класса. Например, язык гарантирует, что конструктор виртуального базового класса вызывается (явно или неявно) ровно один раз — из конструктора полного объекта (то есть из конструктора «наиболее производного класса»). Например: с1ам А ( // ...
): // нет конструктора с1азз В ( риЫ!с: В() ' // ... ) // конструктор но умолчанию с(ам С ( риЫ1с: С(Ы1) ) )' // нет умолчательного конструктора с1азз В: »(гзиа1 риЫ/с А, »(ггиа! риЫ/с В, »/ггиа! риЫ/с С ( В() ( /* ... */ ) // еп ою длл С нет умолчотельного конструктора Э((и11): С(1) ( /* ... */ ); //о)г // ... )) с1азз И'оЫот //... Мгзиа!»оЫ Йаи () ) Пусть также имеются классы, умеющие изменять внешний вид окна и добавлять к нему вспомогательные интерфейсные средства; Конструктор виртуального базового класса выполняется до того, как начинают работать конструкторы производных классов. Там, где это требуется, программист может имитировать такую схему работы, вызывая функции виртуального базового класса лишь из «наиболее производного класса».
Например, пусть имеется класс В1из(ои, знающий, как нужно отрисовывать содержимое окна: 483 15.2. Множественное наследование с(азз Игалов нвй Ьогт!ег: риЬНс Ыггиа! гр!идою //средства длл работы с рамкой юЫ овп Йгав(); // отобразить рамку юЫйан (); с1авв И!и«(ов шЬЬ тени:рибйс Ыггиа1 1г!идою ( //средства длл работы с меню ыоЫ онт Нгав ( ); // отобразить меню ыоЫ дгав ( ); Функции оюи Ыгав() не обязаны быть виртуальными, так как из вызов предполагается выполнять из виртуальных функций йав(), «знающих» тип объекта, для которых они вызваны. Далее, мы можем создать вполне правдоподобный «хронометрический» класс С!ос!с сАги С!оса: риЫ!с И'гидов вЬЬ Ьогбег, риЫтс 67ибов юЬЬ теин ( // средства длк работы с часами юЫ опт Йав(); //отобразить циферблат и стрелки ыо!б Й'ав ( ) ' )' или в графическом виде: И!ибов нвУЬ Ьогг(ег И(пйгв вЬЬ тени С!оса Функции йив() теперь можно написать с использованием функций ови Йав() так, что при вызове любых функций отрисовки функция ИгзаГов::в(гав() будет вызываться ровно один раз.