5.2. Доступ к приватным компонентам
<<Друзья>> в языке C++
Внутри объявления класса в языке C++ можно включать объявление «друже-ственных» (friend) подпрограмм или классов, представляющих собой под-программы или классы, которые имеют полный доступ к приватным данным операциям класса:
class Airplane_Data {
private:
int speed;
friend void proc(const Airplane_Data &, int &);
friend class CL;
};
Подпрограмма ргос и подпрограммы класса CL могут обращаться к приватным компонентам Airplane_Data:
void proc(const Airplane_Data & a, int & i)
{
i = a.speed; // Правильно, мы — друзья
}
Подпрограмма ргос может затем передавать внутренние компоненты класса, используя ссылочные параметры, или указатели, как показано выше. Таким образом, «друг» выставил на всеобщее обозрение все секреты абстракции.
Мотив для предоставления такого доступа к приватным элементам взят из операционных систем, в которых были предусмотрены механизмы явного предоставления привилегий, называемых возможностями (capabilities). Это понятие меньше соответствует языкам программирования, потому что одна из целей ООП состоит в том, чтобы создавать закрытые, пригодные для повторного использования компоненты. Идея «друзей» проблематична с проектной точки зрения, поскольку предполагает, что компонент располагает знанием о том, кто им воспользуется, а это определенно несовместимо с идеей многократного использования компонентов, которые вы покупаете или заимствуете из других проектов. Другая серьезная проблема, связанная с конструкцией friend, состоит в слишком частом использовании ее для «заплат» в программе, вместо переосмысления абстракции. Чрезмерное употребление конструкции friend, очевидно, разрушит абстракции, которые были так тщательно разработаны.
Допустимо применение «друзей», когда абстракция составлена из двух самостоятельных элементов. В этом случае могут быть объявлены два класса, которые являются «друзьями» друг друга. Например, предположим, что классу Keyboard (клавиатура) необходим прямой доступ к классу Display (дисплей), чтобы воспроизвести эхо-символ; и наоборот, класс Display должен быть в состоянии поместить символ, полученный из интерфейса сенсорного экрана, во внутренний буфер класса Keyboard:
class Display {
private:
void echo(char с);
friend class Keyboard; // Разрешить классу Keyboard вызывать echo
};
class Keyboard {
private:
void put_key(char c);
friend class Display; // Разрешить классу Display вызывать put_key
};
Использование механизма friend позволяет избежать как создания неоправданно большого числа открытых (public) подпрограмм, так и объединения двух классов в один большой класс только потому, что они имеют одну-един-ственную общую операцию.
С помощью friend можно также решить проблему синтаксиса, связанную с тем фактом, что подпрограмма в классе C++ имеет отличимый получатель, такой как obj1 при вызове obj1.proc(obj2). Это привносит в подпрограммы асимметрию, в противном случае они были бы симметричны по параметрам. Стандартный пример — перегрузка арифметических операций. Предположим, что мы хотим перегрузить «+» для комплексных чисел и в то же время позволить операции неявно преобразовать параметр с плавающей точкой в комплексное значение:
complex operator + (float);
complex operator + (complex);
Рассмотрим выражение х + у, где одна из переменных (х или у) может быть с плавающей точкой, а другая комплексной. Первое объявление правильно для комплексного х и плавающего у, потому что х+у эквивалентно x.operator+(y), и, стало быть, будет диспетчеризованно отличимому получателю комплексного типа. Однако второе объявление для х+у, где х имеет тип с плавающей точкой, приведет к попытке диспетчеризоваться к операции с плавающей точкой, но операция была объявлена в комплексном классе.
Решение состоит в том, чтобы объявить эти операции как «друзей» класса, а не как операции класса:
friend complex operator + (complex, complex);
friend complex operator + (complex, float);
friend complex operator + (float, complex);
Хотя эта конструкция популярна в языке C++, на самом деле существует лучшее решение, при котором не требуется friend.
Оператор «+=» можно определить как функцию-член (см. справочное руководство, стр. 249), а затем «+» можно определить как обычную функцию за пределами класса:
complex operator + (float left, complex right)
{
complex result = complex(left);
result + = right; // Результат является отличимым получателем
return result;
}
Спецификаторы доступа в языке C++
Когда один класс порождается из другого, мы вправе спросить, имеет ли производный класс доступ к компонентам базового класса. В следующем примере database (база данных) объявлена как приватная, поэтому она недоступна в производном классе:
class Airplanes {
private:
Airplane_Data database [100];
};
class Jets : public Airplanes {
void process Jet(int);
};
void Jets::process_jet(int i)
{
Airplane_Data d = database[i] ; // Ошибка, нет доступа!
};
Если объявлен экземпляр класса Jets, он будет содержать память для database, но этот компонент недоступен для любой подпрограммы в производном классе.
Есть три спецификатора доступа в языке C++:
• Общий (public) компонент доступен для любого пользователя класса.
• Защищенный (protected) компонент доступен внутри данного класса и внутри производного класса.
• Приватный компонент доступен только внутри класса.
В примере, если database просто защищенный, а не приватный член класса, к нему можно обращаться из производного класса Jets:
class Airplanes {
protected:
Airplane_Data database[100];
};
class Jets : public Airplanes {
void process_jet(int);
};
void Jets::process_jet(int i)
{
Airplane_Data d = database[i]; // Правильно, в производном классе
};
Однако это не очень хорошая идея, потому что она ставит под удар абстракцию. Вероятно, было бы лучше даже для производного класса манипулировать унаследованными компонентами, используя общие или защищенные подпрограммы. Тогда, если внутреннее представление изменяется, нужно изменить только несколько подпрограмм.
Язык C++ допускает изменение доступности компонентов класса при объявлении производного класса. Обычно порождение бывает общим (public). Так было во всех наших примерах, и при этом сохранялась доступность, заданная в базовом класс. Однако вы также можете задать приватное порождение, тогда и общие, и защищенные компоненты становятся приватными:
class Airplanes {
protected:
Airplane_Data database [100];
};
class Jets : private Airplanes { // Приватное порождение
void process_jet(int);
};
void Jets::process_jet(int i)
{
Airplane_Data d = database[i]; // Ошибка, нет доступа
};
Пакеты-дети в языке Ada
В языке Ada только тело пакета имеет доступ к приватным объявлениям. Это делает невозможным непосредственное совместное использование пакетами приватных объявлений так, как это можно делать в языке C++ с защищенными объявлениями. В языке Ada 95 для совместного использования приватных объявлений доставлено специальное средство структурирования, так называемые пакеты-дети (child packages). Здесь мы ограничим обсуждение пакетов-детей только для этой цели, хотя они чрезвычайно полезны в любой ситуации, когда вы хотите расширить существующий пакет без его изменения или перекомпиляции.
Зададим приватный тип Airplane_Data, определенный в пакете:
package Airptane_Package is
type Airplane_Data is tagged private;
private
type Airplane_Data is tagged
record
ID:String(1..80);
Speed: Integer range 0.. 1000;
Altitude: Integer 0.. 100;
end record;
end Airplane_Package;
Этот тип может быть расширен в пакете-ребенке:
package Airplane_Package.SST_Package is
type SST_Data is tagged private;
procedure Set_Speed(A: in out SST_Data; I: in Integer);
private
type SST.Data is new Airplane_Data with
record
Mach: Float;
end record;
end Airplane_Package.SST_Package;
Если задан пакет P1 и его ребенок Р1 .Р2, то Р2 принадлежит области родителя Р1, как если бы он был объявлен сразу после спецификации родителя. Внутри закрытой части и теле ребенка видимы приватные объявления родителя:
package body Airplane_Package.SST_Package is
procedure Set_Speed(A: in out SST_Data; I: in Integer) is
begin
A.Speed := I; -- Правильно, приватное поле в родителе
end Set_Speed;
end Airplane_Package.SST_Package;
Конечно, общая часть ребенка не может обращаться к закрытой части родителя, иначе ребенок мог бы раскрыть секреты родительского пакета.
Yandex.RTB R-A-252273-3
- Глава 1
- 1.2. Процедурные языки
- 1.3. Языки, ориентированные на данные
- 1.4. Объектно-ориентированные языки
- 1.5. Непроцедурные языки
- 1.6. Стандартизация
- 1.7. Архитектура компьютера
- 1.8. Вычислимость
- 1.9. Упражнения
- Глава 2
- 2.2. Семантика
- 2.3. Данные
- 2.4. Оператор присваивания
- 2.5. Контроль соответствия типов
- 2.7. Подпрограммы
- 2.8. Модули
- 2.9. Упражнения
- Глава 3
- 3.1. Редактор
- 3.2. Компилятор
- 3.3. Библиотекарь
- 3.4. Компоновщик
- 3.5. Загрузчик
- 3.6. Отладчик
- 3.7. Профилировщик
- 3.8. Средства тестирования
- 3.9. Средства конфигурирования
- 3.10. Интерпретаторы
- 3.11. Упражнения
- Глава 4
- 4.1. Целочисленные типы
- I: Integer; -- Целое со знаком в языке Ada
- 4.2. Типы перечисления
- 4.3. Символьный тип
- 4.4. Булев тип
- 4.5. Подтипы
- 4.6. Производные типы
- 4.7. Выражения
- 4.8. Операторы присваивания
- 4.9. Упражнения
- Глава 5
- 5.1. Записи
- 5.2. Массивы
- 5.3. Массивы и контроль соответствия типов
- Подтипы массивов в языке Ada
- 5.5. Строковый тип
- 5.6. Многомерные массивы
- 5.7. Реализация массивов
- 5.8. Спецификация представления
- 5.9. Упражнения
- Глава 6
- 6.1. Операторы switch и case
- 6.2. Условные операторы
- 6.3. Операторы цикла
- 6.4. Цикл for
- 6.5. «Часовые»
- 6.6. Инварианты
- 6.7. Операторы goto
- 6.8. Упражнения
- Глава 7
- 7.1. Подпрограммы: процедуры и функции
- 7.2. Параметры
- 7.3. Передача параметров подпрограмме
- 7.4. Блочная структура
- 7.5. Рекурсия
- 7.6. Стековая архитектура
- 7.7. Еще о стековой архитектуре
- 7.8. Реализация на процессоре Intel 8086
- 7.9. Упражнения
- Глава 8
- 8.1 . Указательные типы
- 8.2. Структуры данных
- 8.3. Распределение памяти
- 8.4. Алгоритмы распределения динамической памяти
- 8.5. Упражнения
- Глава 9
- 9.1. Представление вещественных чисел
- 9.2. Языковая поддержка вещественных чисел
- 9.3. Три смертных греха
- Вещественные типы в языке Ada
- 9.5. Упражнения
- Глава 10
- 10.1. Преобразование типов
- 10.2. Перегрузка
- 10.3. Родовые (настраиваемые) сегменты
- 10.4. Вариантные записи
- 10.5. Динамическая диспетчеризация
- 10.6. Упражнения
- Глава 11
- 11.1. Требования обработки исключительных ситуаций
- 11.2. Исключения в pl/I
- 11.3. Исключения в Ada
- 11.5. Обработка ошибок в языке Eiffei
- 11.6. Упражнения
- Глава 12
- 12.1. Что такое параллелизм?
- 12.2. Общая память
- 12.3. Проблема взаимных исключений
- 12.4. Мониторы и защищенные переменные
- 12.5. Передача сообщений
- 12.6. Язык параллельного программирования оссаm
- 12.7. Рандеву в языке Ada
- 12.9. Упражнения
- Глава 13
- 13.1. Раздельная компиляция
- 13.2. Почему необходимы модули?
- 13.3. Пакеты в языке Ada
- 13.4. Абстрактные типы данных в языке Ada
- 13.6. Упражнения
- Глава 14
- 14.1. Объектно-ориентированное проектирование
- В каждом объекте должно скрываться одно важное проектное решение.
- 14.3. Наследование
- 14.5. Объектно-ориентированное программирование на языке Ada 95
- Динамический полиморфизм в языке Ada 95 имеет место, когда фактический параметр относится к cw-типу, а формальный параметр относится к конкретному типу.
- 14.6. Упражнения
- Глава 15
- 1. Структурированные классы.
- 15.1. Структурированные классы
- 5.2. Доступ к приватным компонентам
- 15.3. Данные класса
- 15.4. Язык программирования Eiffel
- Если свойство унаследовано от класса предка более чем одним путем, оно используется совместно; в противном случае свойства реплицируются.
- 15.5. Проектные соображения
- 15.6. Методы динамического полиморфизма
- 15.7. Упражнения
- 5Непроцедурные
- Глава 16
- 16.1. Почему именно функциональное программирование?
- 16.2. Функции
- 16.3. Составные типы
- 16.4. Функции более высокого порядка
- 16.5. Ленивые и жадные вычисления
- 16.6. Исключения
- 16.7. Среда
- 16.8. Упражнения
- Глава 17
- 17.2. Унификация
- 17.4. Более сложные понятия логического программирования
- 17.5. Упражнения
- Глава 18
- 18.1. Модель Java
- 18.2. Язык Java
- 18.3. Семантика ссылки
- 18.4. Полиморфные структуры данных
- 18.5. Инкапсуляция
- 18.6. Параллелизм
- 18.7. Библиотеки Java
- 8.8. Упражнения