Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 101
Текст из файла (страница 101)
А глобальные функции (за исключением друзей) такого доступа не имеют: го!И июиЫ Ье теаа(ег (Еа!<Т>* р) Еаз<т>::Е)п!г* 0 = 0; ,(г еггогз Е(з)<Т>кЕ1п)с - закрыто У еггог: Егз(<Т>:.угее - закрыто а =р-у"гее! I/ ... (у(Е)зз<Т>:: Сйипк:: с)зиа)г з(ее >31) (г еггогз Егз)<Т>лфзип)скопил)с згае - закрыто ( У...
) л'... ) В определении класса (когда используется ключевое слово с!азз) по умолчанию все члены закрытые, а в определении структуры (когда используется ключевое слово зггист) все члены по умолчанию открытые (810.2.8). 15.3.1. Защищенные члены классов Рассмотрим пример с иерархией классов 1ИпИозе из 815.2.4.1.
Функции озеп з(газе() были задуманы как строительные блоки для применения в производных классах, но их использование в остальных случаях небезопасно. А функции з1газе(), наоборот, годятся к использованию в любых контекстах. Это различие можно точно выразить разделением общего интерфейса класса ЬПпг1озг на защищенную (ргогесге(1) и открытую (рай|с) части: 490 Глава ) 5. Иерархии классов с(азз )гтйозг п«гЬ Ьогдег ( риЫГс: «)ггиа! «о)д дгаи (); // ...
ргогесгег(: »о!«(опт Йат() /У ... рг!гаге: // представление и т.п. )' Прямой доступ к защищенным членам базового класса разрешен в производном классе лишь для объектов этого же класса: с)ам Ви!Уег ( ргогесгед: слог а (128]; // ... ): с!авз ]лилей ЬиЯег: риЬВс ВиЯег ( /* ... */ ) г с!авв Сусйс Ьиг!ег: риЬВс Виууег ( У... гоЫ Г (]лпаед Ьифег* р ) ( а(0]=0; р->а (0] =0; !' У о!с: доступ к собственному защищенному члену /У еггог: доступ к защищенному члену другого типа Это предохраняет от довольно тонких ошибок, связанных с возможностью одного производного класса портить данные другого производного класса.
19.3.1.1. Применение защищенных членов класса Простая модель «закрытый/открытый» для сокрытия данных хорошо работает в случае конкретных классов Я]0.3). Для иерархий же классов существует два вида пользователей классов: производные классы и «простая публика», От имени этих пользователей и производят свои действия над классовыми обьектами функции-члены класса и друзья. Модель «закрытый/открытый» позволяет программисту различать разработчиков классов и обычных пользователей, но она не предоставляет ничего особого в обслуживании производных классов.
Защищенные члены классов более подвержены неправильному использованию, чем закрытые члены. Часто их наличие свидетельствует об ошибках проектирования. Помещение в защищенные секции общих классов заметного объема данных, доступных производным классам, открывает возможность для порчи этих данных. Еше того хуже, защищенные данные, как и открытые, намного труднее реструктурировать ввиду сложности нахождения всех случаев их использования. А это усложняет процесс сопровождения программных систем.
491 15.3. Контроль доступа По счастью, использовать защищенные данные необязательно; наиболее»редпочтительным вариантом являются закрытые данные, что и имеет место по умолчанию. По моему опыту, всегда имеются альтернативы помещению изрядного количества информации в об|ций класс для ее непосредственного использования в производных классах. Обратите внимание, что все это не имеет отношения к защищенным функциям — они прекрасно задают операции для применения их в производных классах.
Иллюстрацией служит класс Тга! в(Нег из 912.4.2. Если бы в этом примере функции класса были бы закрытыми, стало бы невозможно создавать дальнейшие производные классы. Технические примеры доступа к защищенным членам приведены в 9С.11.1. 15.3.2. Доступ к базовым классам Аналогично членам класса, можно сам базовый класс объявить в процессе наследования закрытым (рг1гаге), защищенным (ргагесгед) или открытым (рий11с). Например; с1аьв Х: риЫ1с В ( /* ... */ ); е1авв У: ргогеегед В ( /* ... */ ); е)авв У: ргйаге В ( /* ...
*/ ); Открытое наследование делает производный класс подтипом базового; это наиболее распространенная форма наследования. Защищенную и закрытую формы наследования применяют для использования деталей реализации. Защищенное наследование полезно в случае классовых иерархий, в которых построение дальнейших производных классов является нормой; класс Тва! в1Ыег из 912.4.2 может служить хорошим примером. Закрытое наследование применяется для построения производных классов, закрывающих доступ к интерфейсу базового класса и обеспечивающих большие гарантии, чем базовый класс.
Например, шаблон )гесгаг<Т ь добавляет проверку типа к своему базовому классу Ресгаг<гвЫ" > (913.5). Также, если бы нам потребовалась гарантия того, что всегда доступ к векторам проверяется (см. 93.7.2), то базовый класс для Гес пришлось бы объявить закрытым (чтобы предотвратить преобразование из Гее в непроверяемый базовый класс гесгаг): гет)наге<ейвв Т> сйвв 1'ес: ргйчие гесюг<Т> ( /* ...
*/ ); Л вектор с яров<рвай дианазона Если в определении класса опустить спецификатор режима наследования. то для ключевого слова с(авт но умолчанию режим наследования будет считаться закрытым (ргйаге), а для ключевого слова вггисг он будет считаться открытым (риЫ11С. Например: с1аьв ЛХ: В ( /* ... */ ); //закрытое наследование от В вггиег Л'.
В ( /* ... */ ); //открытое наследование от В Чтобы код бьп более читаемым, лучше явно использовать спецификатор для режима наследования. Этот спецификатор управляет доступом к членам базового класса и преобразованием указателей и ссылок из типа производного класса к типу базового класса. Рассмотрим класс Р, производный от базового класса В: Глава 15.
Иерархии классов 492 ° Если В является закрытым базовым классом, его открьпые и защищенные члены могут использоваться только функциями-членами и друзьями Р. Только функции-члены и друзья класса Р могут преобразовывать Р* в В*. ° Если В является защищенным базовым классом, его открытые и защищенные члены могут использоваться только функциями-членами и друзьями Р, а также функциями-членами и друзьями классов, производных от Р. Только функции-члены и друзья класса Р, а также функции-члены и друзья классов, производных от Р, могут преобразовывать Р* в В*.
° Если В является открытым базовым классом, его открытые члены могут использоваться любой функцией, Кроме того, его защищенные члены могут использоваться функциями-членами и друзьями Р, а также функциями-членами и друзьями классов, производных от Р. Любая функция может преобразовывать Р* в В*. 15.3.2.1. Множественное наследование и контроль доступа Если некоторое имя или базовый класс в принципе доступны по нескольким путям в рамках иерархии множественного наследования, то они достижимы лишь в случае, когда достижимы по любому из этих путей. Например; ыгися В ( я!або ии ят; //...
)' сгаяя В1: риЫ!с Ызтиа1 В ( /* ... */ ); сгаяя Р2: риЫ!с гигиа1 В 1 / * ... */ ); с1аяя ВВ: риьдс Р1, рычаге В2 ( /* ... */ ) ' ВВ* рд = иет РРг В*рЬ =рдг 1пя П = рд- >т; //о/с доступ через Р! // о/с: доступ через Р1 Если некоторая единичная сущность доступна по нескольким путям, мы все равно можем обращаться к ней без возникновения неоднозначностей. Например: с1аяяХ1: риЫ!с В 1 /* ...
*/ ); с1аяя Х2: риЫ!с В ( /* ... */ ); с1аяя ХХ: риЫ1с Х1, риЫ/с Х2 ( /* ... */ ); ХХ* рхх = псы ХХ; Это, в основном, является переформулировкой правил для доступа к членам (915.3). Мы выбираем режим наследования (режим доступа к базовым классам) по тем же соображениям, что и режим доступа к членам. Например, я предпочел сделать ВВнчпВоп защищенным базовым классом для 1га! яВВег (512.4.2), потому что ВВнчЫои служит частью реализации 1га1 я!Ыег, а не частью его интерфейса. Я, однако, не стал полностью закрывать ВВ)г!прои (то есть наследовать в режиме рпчаге), так как хотел оставить возможность получать классы, производные от 1га1 яВг!ег, которым тоже требуется доступ к реализации. Технические примеры доступа к базовым классам приведены в 5С.)1.2.
15.4. Механизм йТП (йоп-Т)гпе Туре)п1оггпайоп) 493 )пг 11 = рхх->гя; 1пг г2 = рхх->зтг 15.3.2.2. Множественное наследование и контроль доступа Объявление из)пВ не может использоваться для получения доступа к дополнительной информации. Оно просто делает доступную информацию более удобной для применения. С другой стороны, если доступ к информации имеется, его можно дать и другим пользователям.
Например: сьззз В ( р«гяа1е: гпг а; р«огесгеЫ: гпг Ь; риЬВс: гпг с; )' «У е««огс В::а - закрыто гУ делает В::Ь оби)едоступнььм через 1З Комбинируя объявления из1пВ с закрытым или защищенным режимами наследования, можно явно открывать доступ к некоторому подмножеству членов класса.
Например: гу даем доступ к В::Ь и В:гс, но не к Вг:а с!авз ВВ: р«дзиге В 1 исдпд В:: Ьг из)пя В:: с; )г См. также 915.2.2. 15.4. Механизм !ПТ! (Кип-Типе Туре !п1оппабоп) Вероятным использованием семейства классов Ба! Ьох, определенных в 912.4, будет передача их системе, управляющей экраном, чтобы та вернула объекты этого типа в момент наступления некоторых событий. Так работает большинство систем графических пользовательских интерфейсов. Но эти системы ничего не знают о типах семейства Ьа! Ьох, так как задаются в терминах своих собственных классов и объектов, а не классов нашего приложения.
В результате возникает неприятный эффект потери типа объектов, которые мы передаем системе, а потом получаем их от нее назад. Восстановление потерянного типа объекта может заключаться в возможности как-то спросить объект о его типе. Для работы с объектами мы обычно располагаем с1аев Вн риЫс В г ридтг: изгпя В::а; игдпд В::Ьг )г У е««о«, неоднозначность: ХХ::Х1 г:В::т или ХХчХ2::В::т У о)с' единственный В::зт в ХХ 494 Глава 15. Иерархии классов подходящего типа указателями или ссылками на них. Поэтому наиболее очевидной и полезной операцией над объектами на этапе выполнения программы будет операция преобразования к ожидаемому типу, которая возвращает корректное значение указателя только если такое преобразование выполнимо (или нулевой указатель в противном случае).