Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 15
Текст из файла (страница 15)
Отмечу использование закрытого (по умолчанию, в отсутствие ключевого слова риЬ11с в спецификации базового класса 11п)с; см. раздел 2.10) наследования. Если разрешить использовать ыогс]11п)с как просто 11п]с, то была бы поставлена под угрозу безопасность работы с типами. Для обеспечения обобщенных типов использовались макросы. Вот как об этом написано в [Бггоцяггнр, 1982Ь]: ИИИИВИИН Производные классы Для это~о в языке нет прямой поддержки, но того же эффекта можно достигнуть с помощью стандартного препроцессора С. Например: с1авв ЕЬЕМ вСасК ( еьем * и1п, * сор, * Мак) но1с) пеы(1пС), г)е1еке(чо1г))) рпЫ1с г уо1с) рпвп(ЕЬЕМ)г еьем рор(чо1г))г ): йс)ег1пе ЕЬЕМ 1опд Ие61пе ЕЬЕМ ятасК 1опд яСасК $1пс1аг)е "ясасК. Ь" зппг)ес ЕЬЕМ йппс)ес ЕЬЕМ вСасК сурет)еь с1авя к Х; зг)еЕ1пе ЕЬЕМ Х зг)е11пе ЕЬЕМ вСасК Х вСасК 41пс1иг)е "вСасК.П" йппдеГ ЕЬЕМ зипг)ес ЕЬЕИ. яСасК с1авв 1опд ясасК 1я(1024]г с1авв 1апд ясасК 1в2(512); с1авя х всасК хв(512); Конечно, этот способ далек от совершенство, зато просты Это был один из самых ранних и самых грубых методов.
Для использования в реальных программах он не годился, так как был причиной слишком многих ошибок, поэтому вскоре я определил несколько «стандартных» макросов, «склеивающих лексемы», и порекомендовал применять их для реализации обобщенных классов (Ясгоцвггцр, 1986, 97.3.5). В конечном итоге отсюда взяли свое начало шаблоны С++ и техника их совместного использования с базовыми классами для выражения общих свойств инстанцированных шаблонов (см. раздел 15.5). 2.9.3. Модель размещения объекта в памяти Реализация производного класса заключалась в объединении его членов и членов базового классов. Например, если есть объявления с1авв й ( 1пс а; раЫЕс: г'* Функции-члены *г' ); Это объявление можно поместит~ в заголовочный файл и с помощью макросов расширять для каждого типо еьем, который предполагается испол»заветы Язык С )/(/!Ф С!аззез 1ИИИИИИ11 с1авв В : рпЬ11с а ( (пг Ь; рпЬ11с: /* функции-члены */ ); то объект класса В будет представлен структурой /* сгенерированный С-кол */ всгцсс в ( тпс а; (пг Ь; ); то есть апг а 1пг Ь Конфликты между именами членов базового и производного классов устранялись самим компилятором, который присваивал нм подходящие индивидуальные имена.
Вызовы обрабатывались так, как если бы никакого наследования не была По сравнению с С вовсе не возникало дополнительного расхода времени илн памяти. 2.9.4. Ретроспектива Было ли разумно избегать виртуальных функций в С ъч()) С!аээеэу Да, язык и без них был полезен, а их отсутствие позволило на некоторое время отложить длительные дебаты о полезности, правильном использовании и эффективности. За это время были разработаны такие языковые механизмы и методы программирования, которые оказались кстати даже при наличии более мощных механизмов наследования.
Эти механизмы и методы использовались как противовес стремлению некоторых программистов применять исключительно наследование, как будто больше в языке ничего не было (см. раздел 14.2.3). В частности, классы использовались для реализации таких конкретных типов, как союр1ех и зсх1пд, и интерфейс классов завоевал популярность. Класс эсас)с, реализованный как интерфейс к более общему классу с)ее цеце, — это пример наследования без виртуальных функций.
Нужны лн были виртуальные функции в С гуй!г С1аэзез для решения тех задач, на которые он был нацелен? Да, поэтому они и были включены в первое из важнейших расширений при переходе к С++. 2.10. Модель защиты Перед тем как приступить к созданию С гу)1)) С!аззез, я работал с операционными системами. Происхождение механизмов защиты в С++ следует искать в концепции зашиты в Кембриджском компьютере САР и аналогичных системах, а не в каких-то работах по языкам программирования. Единицу защиты составляет 16ИИИИИИН Модель защиты с1авв Х ( /* представление */ рп)о11с: чо1с) Г(); /* функция-член, имеющая доступ к представлению */ Гхгепд чоха о(); /* глобальная функция, имеющая доступ к */ /* представлению */ ); Первоначально дружественными могли быть только классы, то есть всем функциям-членам одного класса предоставлялся доступ ко всем членам другого класса, в объявлении которого первый класс имел спецификатор х хфепс1.
Но позже было сочтено, что удобно предоставлять такой доступ и отдельным функциям, в частности, глобальным (см. также раздел 3.6.1). Объявление дружественности виделось как механизм, аналогичный доменам защиты (ргогесиоп г)оша)п), предоставляющим право чтения-записи другим объектам. Это явная часть объявления класса. Поэтому я никогда не усматривал в повторяющихся утверждениях о том, что объявление бхфепс(, дескать, анарушает инкапсуляцию», ничего, кроме невежества и путаницы в терминологии. Даже в первой версии С ччгЬ С1аззез модель защиты применялась как к базовым классам, так и к членам.
Это означает, что производный класс мог наследовать базовому открыто или закрыто. Введение открытого и закрытого наследования базовых классов примерно на пять лет опережает дебаты по поводу наследования интерфейса и реализации 13пу()ег, 1986], (ь)з)гоч, 1987). Если вы хотите наследовать только реализацию, пользуйтесь в С++ закрытым наследованием. Открытое наследование дает пользователям производного класса доступ ко всему интерфейсу, предоставляемому базовым классом.
При закрытом наследовании базовый класс можно очи~ать деталью реализации, даже его открытые члены недоступны иначе как через интерфейс, предоставленный производным классом. Для того чтобы иметь аполупрозрачные области действия», был предложен механизм, разрешающий доступ к отдельным открытым членам из закрыто наследуемого базового класса [Бггоцзггпр, 1982Ь1: с1авв чесхох ( /* ... */ роъ1хс: /* */ чей рхг с(чогг)); ); класс, и фундаментальное правило гласит, что нельзя предоставить доступ к классу самому себе.
Только явные объявления, помещенные в объявление класса (предположительно его создателем), могут разрешить доступ. По умолчанию вся информация закрыта. Для предоставления доступа к члену его следует поместить в открытой (риЫ)с) части объявления класса либо объявить функцию или класс дружественными с помощью ключевого слова йхфепс1. Например: Язык С ч)/(Ф С!аззез ИИИИИИВИ; с1авв Павйес): чессог /* чесгог - закрыто наследуемый базовый */ /* класс для лавлеб */ ( /* */ реп)1с: чесгог.ргдлс; /* полупрозрачная область действия */ /* другие функции из класса чесгог не */ /* могут применяться к объектам класса )тавлед */ /* */ ): Синтаксически для того, чтобы сделать недоступное имя доступным, нужно просто назвать его.
Это пример абсолютно логичного, минимального и недвусмысленного синтаксиса. Но при этом он малопонятен; почти любой другой вариант был бы лучше. Указанная синтаксическая проблема ныне решена с помощью пв)пй-объявлений (см. раздел 17.5.2). В книге [АКМ) следуюшим образом резюмирована концепция защиты в С++: с) защита проверяется во время компиляции и направлена против случайных, а не преднамеренных или явных попыток ее преодоления; ш доступ предоставляется классом; д контроль прав доступа выполняется для имен и не зависит от типа именованной сущности; (з единицей защиты является класс, а не отдельный объект; (з контролируется доступ, а не видимость. Все это было верно и в 1980 г., хотя теперь терминология слегка изменилась.
Последний пункт проще объяснить на примере: // глобальная переменная а тлс а; с1авв х ( ргдчасе: 1пг а; // член х::а )' с1авв ХХ : рцб)1с Х ( чей 1[) ( а = 1; ) // какое ат )! Если бы контролировалась видимость, то член х:: а был бы невидим н хх:: й () ссылалась бы на глобальную переменную а. На самом деле в С ччй)з С!аваев и С++ считается, что глобальная а скрыта за недоступным х:: а, поэтому хх:: г' ( ) приводит к ошибке компиляции из-за попытки получить доступ к недоступному члену х:: а. Почему я ввел именно такое определение и был ли прав? Я не очень хорошо помню ход своих мыслей, а в сохранившихся записях об этом ничего нет.
Припоминаю только один разговор на эту тему. Для приведенного выше примера принятое правило гарантирует, что й ( ) всегда будет ссылаться на одно и то же, какой бы доступ ни был объявлен для Х:: а. Если бы ключевые слова рцЬ11с и ргйчасе контролировали видимость„а не доступ, то изменение риЬ11с на ргдчасе без предупреждения изменило бы семантику программы (вместо Х:: а 16ИИВИШИИ Гарантии времени исполнения дтасв ХггсгвпС1 ассевв Со угга, уг:Ь, апб у:гд(спас)г Мне не нравились все эти предложения, потому что более скрупулезный контроль не дает никакой дополнительной зашиты. Любая функция-член может модифицировать любой член-данные класса, поэтому функция, которой предоставлен доступ к функции-члену, может косвенно модифицировать любой член. Я считал и считаю, что усложнение спецификации, реализации и использования не перевешивает преимуществ более явного контроля доступа.