Лекция 7:

к оглавлению
оглавление

Дружественные функции

Доступ к защищенным элементам класса

Как вы, наверное, помните, одно из основных требований для написания хороших программ с использованием объектно-ориентированного подхода состоит в том, чтобы максимально исключить возможность манипулирование элементами данными напрямую. Вместо этого должен быть представлен достаточно широкий набор методов, через которые должен осуществляться доступ к данным для их наблюдения, модификации и др.

Однако иногда использование методов для доступа к элементам данным может оказаться неудобным и неэффективным (поскольку на вызов метода требуется некоторое время). При этом речь может идти о доступе к элементам данным различных классов.

Например, рассмотрим новую функцию для нашего старого примера. Ее суть состоит в том, чтобы определить, лежит ли некоторый объект типа Point внутри области, занимаемой некоторым объектом типа Circle. Иными словами, нужно определить находится ли точка внутри окружности.

Для решения этой задачи нужны:
- координаты точки,
- координаты центра,
- радиус окружности.

Как видите все необходимые для этого данные лежат в областях private и protected.

Можно, конечно, написать новый метод для класса Circle, типа

Boolean Circle::IsInside(Point &P)
{
 if ((X-P.GetX())*(X-P.GetX())+(Y-P.GetY())*(Y-P.GetY())<= R*R) return true;
  else return false;
}

Но можно реализовать такую процедуру по-другому. И тому можно найти оправдание. Во-первых, вызов методов GetX() и GetY() для точки P можно здесь считать не очень элегантным, в чем-то даже громоздким. Было бы эффективнее непосредственно обращаться к X и Y. Однако если вы помните X и Y объявлены в разделе protected. Во вторых, методы определяются как процедуры для наблюдения и модификации элементов данных и вроде как, написание метода IsInside() не совсем оправдано, поскольку он не отвечает требованиям, предъявляемым к методам.

Тогда как же можно реализовать функцию, не являющуюся методом класса, но в тоже время, имеющую доступ к необщим членам класса.

Для того чтобы класс мог предоставлять функциям–неэлементам доступ к своей приватной части используется механизм объявления, так называемых, функций–друзей (friend) класса или дружественных функций. Для этого в декларации класса помещается декларация функции, перед которой стоит ключевое слово friend.

Один особенно полезный аспект механизма friend состоит в том, что функция может быть другом двух и более классов.

Посмотрим, как можно использовать механизм дружественных функций в нашем примере.

Для начала дополним декларацию классов Point и Circle:

class Point
{
//...
 friend Boolean IsInside (Circle &C, Point &P);
};

class Circle: public Point
{
//...
 friend Boolean IsInside (Circle &C, Point &P);
};

Заметьте, что объявление дружественной функции
friend Boolean IsInside (Circle &C, Point &P);
можно с одинаковым эффектом вставлять в любом месте в декларации класса, это может быть любой раздел (public, protected или даже private).

Сама функция будет:

Boolean IsInside(Circle &C, Point &P)
{
 if ((C.X-P.X)*(C.X-P.X)+(C.Y-P.Y)*(C.Y-P.Y)<= C.R * C.R) return true;
  else return false;
}

Таким образом, функция IsInside(), обычная функция (обычная в том смысле, что не является функцией элементом какого-то класса) получает доступ к приватной и защищенной областям классов Point и Circle. Обеспечить возможность такого доступа можно только в декларации класса. Так что в этом смысле инкапсуляция и защищенность данных сохраняется, поскольку полностью исключается всякая возможность такого доступа так, чтобы класс об это "не знал".

Теперь несколько слов о вариантах проявления механизма дружественных функций

Функция-элемент одного класса может быть дружественной иному классу. Например

class x
{
//...
void f();
};

class y
{
//...
friend void x::f();
};

Не исключен и такой вариант, когда все функции одного класса – дружественны другому классу. Для такого случая есть даже сокращенная запись

class y
{
//...
friend class x;
}

[к началу]

Переопределение операторов с помощью дружественных функций

Механизм объявления дружественных функций часто используется при переопределении операторов.

Давайте вспомним, как раньше мы переопределяли операторы для класса _3d

class _3d
{
//...
 _3d operator + (_3d b);
};

_3d _3d::operator + (_3d b)
{
 _3d c;
 c.x = x + b.x;
 c.y = y + b.y;
 c.z = z + b.z;
 return c; }

А теперь посмотрим, как тот же самый оператор + можно переопределить с использованием дружественной функции.

class _3d
{
//...
 friend _3d operator + (_3d &a, _3d &b);
};

_3d operator + (_3d &a, _3d &b)
{
 _3d c;
 c.x = a.x + b.x;
 c.y = a.y + b.y;
 c.z = a.z + b.z;
 return c;
}

Давайте обсудим, в каких случаях для доступа к приватной части типов следует использовать элементы, а в каких – дружественные функции. Некоторые операции должны быть элементами – конструкторы, деструкторы и виртуальные функции, но, как правило, программист имеет возможность выбора.

При определении того, какой должна быть функция нужно руководствоваться простым правилом: операция, изменяющая состояние объекта класса, должна быть элементом, а не другом.

Все вышесказанное в полной мере относится и к способу переопределения операторов. Для операторов вроде "=", "++" и "+=" выражение слева ясно определено, и, по-видимому, естественно реализовать их как операции над объектом, обозначенным этим выражением. Напротив, если для всех операндов операции желательно иметь неявное преобразование, функция, реализующая его, должна быть дружественной, а не элементом. Часто именно так обстоит дело для функций, реализующих операторы "+", "–", "||" и др.

Например,

class X {...};
X x;
x=1+x;  // т.е. x = 1.operator + (x); – ошибка

В этом случае использование функции элемента для переопределения оператора "+" в любом случае породит ошибку. Так что здесь было бы правильнее применять дружественную функцию.

Если преобразования типов не определяются, то, по-видимому, нет явных причин для предпочтения элемента дружественной функции. При прочих равных условиях, выбирайте элемент.

[к началу]
назад КОНЕЦ СЕДЬМОЙ СЕРИИ вперед