Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 14 15 16 [ 17 ] 18 19 20 ... 144


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 для классов, имеющих конструктор, был реализован просто как



1 ... 14 15 16 [ 17 ] 18 19 20 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика