|
Программирование >> Полиморфизм без виртуальных функций в с++
class hashed : vector /* vector - закрыто наследуемый базовый */ /* класс для hashed */ /* ... */ public: vector.print; /* полупрозрачная область действия */ /* другие функции из класса vector не */ /* могут применяться к объектам класса hashed */ /* ... */ Синтаксически для того, чтобы сделать недоступное имя доступным, нужно просто назвать его. Это пример абсолютно логичного, минимального и недвусмысленного синтаксиса. Но при этом он малопонятен; почти любой другой вариант был бы лучше. Указанная синтаксическая проблема ныне решена с помощью using-объявлений (см. раздел 17.5.2). В книге [ARM] следующим образом резюмирована концепция защиты в С++: □ защита проверяется во время компиляции и направлена против случайных, а не преднамеренных или явных попыток ее преодоления; □ доступ предоставляется классом; □ контроль прав доступа выполняется для имен и не зависит от типа именованной сущности; □ единицей защиты является класс, а не отдельный объект; □ контролируется доступ, а не видимость. Все это было верно и в 1980 г., хотя теперь терминология слегка изменилась. Последний пункт проще объяснить на примере: int а; глобальная переменная а class X { private: int а; член X: :а class XX : public X { void f{) { a = 1; } какое a? Если бы контролировалась видимость, то член X: : а был бы невидим и XX: : f () ссылалась бы на глобальную переменную а. На самом деле в С witli Classes и С++ считается, что глобальная а скрыта за недоступным Х: : а, поэтому XX: : f () приводит к ошибке компиляции из-за попытки получить доступ к недоступному члену X: : а. Почему я ввел именно такое определение и был ли прав? Я не очень хорошо помню ход своих мыслей, а в сохранившихся записях об этом ничего нет. Припоминаю только один разговор на эту тему. Для приведенного выше примера принятое правило гарантирует, что f () всегда будет ссылаться на одно и то же, какой бы доступ ни был объявлен для X: :а. Если бы ключевые слова public и private контролировали видимость, а не доступ, то изменение public на private без предупреждения изменило бы семантику программы (вместо X: :а использовалось бы глобальное а). Я больше не считаю этот аргумент решающим (если и считал раньше), но такой ход оказался полезным, поскольку позволяет программисту добавлять и удалять во время отладки public и private, не меняя смысла профаммы. Любопытно, была ли эта сторона определения C-I-+ осознанным решением. Может быть, это просто побочный результат препроцессорной технологии, использовавшейся во времена С with Classes, который не пересматривался при реализации настоящего ко.мпилятора во время перехода к C-H-i- (см. раздел 3.3). Другой аспект механизма зашиты в С++, свидетельствующий о влиянии операционных систем, - это отношение к нарушению правил. Я думаю, что любой компетентный профаммист может обойти любое правило, которое не поддержано ап-паратно, так что не стоит даже пытаться защититься от мошенничества [ARM]: Механизмы контроля доступа в С++ обеспечивают защиту от случойности, а не от преднамеренного обмана. Любой язык программирования, который поддерживоет прямой доступ к памяти, обязательно остовляет любой элемент данных открытым для сознательного жульничества , норушоющего явные правила типов, сформулированные для этого элементо . Задача механизма защиты - гарантировать, что любое такое действие в обход системы типов выражено явно, и свести к минимуму необходимость в таких нарушениях. Пришедшая из операционных систем концепция защиты от чтения/записи превратилась в С++ в понятие о const (см. раздел 3.8). За прошедшие годы было много предложений о том, как предоставить доступ к единице, меньшей, чем целый класс. Например: grant X::f(int) access to Y::a, Y::b, and Y::g(char); Мне не нравились все эти предложения, потому что более скрупулезный контроль не дает никакой дополнительной защиты. Любая функция-член может модифицировать любой член-данные класса, поэтому функция, которой предоставлен доступ к функции-члену, может косвенно модифицировать любой член. Я считал и считаю, что усложнение спецификации, реализации и использования не перевешивает преимуществ более явного контроля доступа. 2.11. Гарантии времени исполнения Описанные выше механизмы контроля доступа просто предотвращают неразрешенный доступ. Другой вид гарантий предоставляется такими специальными функциями-членами , как конструкторы, которые компилятор распознает и неявно вызывает. Идея заключалась в том, чтобы дать программисту средства фор.мулировать гарантированные условия (их еще называют инвариантами), на выполнение которых другие функции-члены могли бы рассчитывать (см. также раздел 16.10). 2.11.1. Конструкторы и деструкторы в то время я часто объяснял эту концепцию так: функция new (конструктор) создает среду, в которой работают другие функции-члены, а функция delete (деструктор) эту среду уничтожает и освобождает выделенные для нее ресурсы. Например: class monitor : object { /* ... */ public: newO { /* создать защелку для монитора */ } delete О { /* освободить и удалить защелку */ } /* ... */ См. также разделы 3.9 и 13.2.4. Откуда возникла концепция конструктора? Подозреваю, что ее изобрел я сам. Я был знаком с мехапизмо.м инициализации объекта класса в Simula. Однако я смотрел на объявление класса прежде всего как па определение интерфейса, так что хотел избежать помеп1ения в него кода. Поскольку в С with Classes вслед за С было три класса хранения, то какой-то вид функций инициализации почти неизбежно должен был распознаваться компилятором (см. раздел 2.11.2). Скоро выяснилось, что было бы полезно иметь несколько конструкторов. Это наблюдение послужило причиной появления механизма перегрузки в С++ (см. раздел 3.6). 2.11.2. Распределение памяти и конструкторы Как и в С, память для объектов .можно выделять тремя способами: из стека (автоматическая память), по фиксированному адресу (статическая па.мять) и из свободной па.мяти (куча или динамическая память). В любо.м случае для созданного объекта должен быть вызван конструктор. В С раз.мещепие объекта в свободной памяти требует только вызова функции распределения. Например: monitor* р = (monitor*)malloc(sizeof(monitor)); Очевидно, что для С with Classes этого недостаточно, так как нельзя гарантировать вызов конструктора. Поэтому я ввел оператор, отвечающий одновременно за выделение памяти и инициализацию: monitor* р = new monitor; Я назвал этот оператор new, поскольку так назывался аналогичный оператор в Simula. Оператор new вызывает некую функцию для выделения памяти, а затем конструктор для инициализации этой памяти. Такая комбинированная операция называется порождением или просто созданием объекта; она создает объект в неинициализированной области памяти. Нотация, вводимая оператором new, очень удобна. Однако объединение выделения и инициализации памяти в одной операции без явного механизма извещения об ошибках на практике привело к некоторым сложностям. Обработка ошибок, происшедших в конструкторе, редко бывает конструктивно важной, однако с введением исключений (см. раздел 16.5) было найдено общее решение и этой проблемы. Чтобы свести к миии.муму необходимость повторной компиляции, в Cfront оператор new для классов, имеющих конструктор, был реализован просто как
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |