logo search
Языки программирования

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;

Конечно, общая часть ребенка не может обращаться к закрытой части родите­ля, иначе ребенок мог бы раскрыть секреты родительского пакета.