Лекция 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); – ошибка
В этом случае использование функции элемента для переопределения оператора "+" в любом случае породит ошибку. Так что здесь было бы правильнее применять дружественную функцию.
Если преобразования типов не определяются, то, по-видимому, нет явных причин для предпочтения элемента дружественной функции. При прочих равных условиях, выбирайте элемент.
КОНЕЦ СЕДЬМОЙ СЕРИИ |