Функции друзья
В C++ существует возможность разрешить доступ к закрытым членам класса функциям, которые не являются членами этого класса. Для этого достаточно объявить эти функции "дружественными" (или "друзьями") по отношению к рассматриваемому классу. Чтобы сделать функцию "другом" класса, включите ее прототип в public-раздел объявления класса и предварите его ключевым словом friend. Например, в этом фрагменте кода функция frnd() объявляется "другом" класса cl.
class cl {
// . . .
public:
friend void frnd(cl ob);
// . . .
};
Ключевое слово friend предоставляет функции, которая не является членом класса, доступ к его закрытым членам.
Как видите, ключевое слово friend предваряет остальную часть прототипа функции. Функция может быть "другом" нескольких классов. Рассмотрим короткий пример, в котором функция-"друг" используется для доступа к закрытым членам класса myclass.
// Демонстрация использования функции-"друга".
#include <iostream>
using namespace std;
class myclass {
int a, b;
public:
myclass(int i, int j) { a=i; b=j; }
friend int sum(myclass x); // Функция sum() - "друг" класса
myclass.
};
// Обратите внимание на то, что функция sum() не является членом
ни одного класса
int sum(myclass х)
{
/* Поскольку функция sum() — "друг" класса myclass, она имеет
право на прямой доступ к его членам данных а и b. */
return x.a + x.b;
}
int main ()
{
myclass n (3, 4);
cout << sum(n);
return 0;
}
В этом примере функция sum() не является членом класса myclass. Тем не менее она имеет полный доступ к private-членам класса myclass. В частности, она может непосредственно использовать значения х.а и х.b. Обратите также внимание на то, что функция sum() вызывается обычным образом, т.е. без привязки к объекту (и без использования оператора "точка"). Поскольку она не функция-член, то при вызове ее не нужно квалифицировать с указанием имени объекта. (Точнее, при ее вызове нельзя задавать имя объекта.) Обычно функции-"другу" в качестве параметра передается один или несколько объектов класса, для которого она является "другом", как в случае функции sum().
Несмотря на то что в данном примере мы не извлекаем никакой пользы из объявления функции sum() "другом", а не членом класса myclass, существуют определенные обстоятельства, при которых статус функции-"друга" имеет большое значение. Во-первых, функции-"друзья" могут быть полезны для перегрузки операторов определенных типов. Вовторых, функции-"друзья" упрощают создание некоторых функций ввода-вывода. Об этом речь впереди.
Третья причина использования функций-"друзей" состоит в том, что в некоторых случаях два (или больше) класса могут содержать члены, которые находятся во взаимной связи с другими частями программы. Например, у нас есть два различных класса, которые при возникновении определенных событий отображают на экране "всплывающие" сообщения. Другие части программы, которые предназначены для вывода данных на экран, должны знать, является ли "всплывающее" сообщение активным, чтобы случайно не перезаписать его. В каждом классе можно создать функцию-член, возвращающую значение, по которому можно судить о том, активно сообщение или нет; однако проверка этого условия потребует дополнительных затрат (т.е. двух вызовов функций вместо одного). Если статус "всплывающего" сообщения необходимо проверять часто, эти дополнительные затраты могут оказаться попросту неприемлемыми. Однако с помощью функции, "дружественной" для обоих классов, можно напрямую проверять статус каждого объекта, вызывая только одну функцию, которая будет иметь доступ к обоим классам. В подобных ситуациях функция-"друг" позволяет написать более эффективный код. Эта идея иллюстрируется на примере следующей программы.
// Использование функции-"друга".
#include <iostream>
using namespace std;
const int IDLE=0;
const int INUSE=1;
class С2; // опережающее объявление
class C1 {
int status; // IDLE если сообщение неактивно, INUSE если
сообщение выведено на экран.
// ...
public:
void set_status(int state);
friend int idle(C1 a, C2 b);
};
class C2 {
int status; // IDLE если сообщение неактивно, INUSE если
сообщение выведено на экран.
// ...
public:
void set_status(int state);
friend int idle(C1 a, C2 b);
};
void C1::set_status(int state)
{
status = state;
}
void C2::set_status(int state)
{
status = state;
}
// Функция idle() - "друг" для классов C1 и C2.
int idle(C1 a, C2 b)
{
if(a.status || b.status) return 0;
else return 1;
}
int main()
{
C1 x;
C2 y;
x.set_status(IDLE);
у.set_status(IDLE);
if(idle(x, y)) cout << "Экран свободен.\n";
else cout << "Отображается сообщение.\n";
x.set_status(INUSE);
if(idle(x, y)) cout << "Экран свободен.\n";
else cout << "Отображается сообщение.\n";
return 0;
}
При выполнении программа генерирует такие результаты.
Экран свободен.
Поскольку функция idle() является "другом" как для класса С1, так и для класса С2, она имеет доступ к закрытому члену status, определенному в обоих классах. Таким образом, состояние объекта каждого класса одновременно можно проверить всего одним обращением к функции idle().
Опережающее объявление предназначено для объявления имени классового типа до определения самого класса.
Обратите внимание на то, что в этой программе используется опережающее объявление (также именуемое опережающей ссылкой) для класса С2. Его необходимость обусловлена тем, что объявление функции idle() в классе С1 использует ссылку на класс С2 до его объявления. Чтобы создать опережающее объявление для класса, достаточно использовать формат, представленный в этой программе.
"Друг" одного класса может быть членом другого класса. Перепишем предыдущую программу так, чтобы функция idle() стала членом класса С1. Обратите внимание на использование оператора разрешения области видимости (или оператора разрешения контекста) при объявлении функции idle() в качестве "друга" класса С2.
/* Функция может быть членом одного класса и одновременно
"другом" другого.
*/
#include <iostream>
using namespace std;
const int IDLE=0;
const int INUSE=1;
class C2; // опережающее объявление
class C1 {
int status; // IDLE, если сообщение неактивно, INUSE, если
сообщение выведено на экран.
// ...
public:
void set_status(int state);
int idle(C2 b); // теперь это член класса C1
};
class C2 {
int status; // IDLE, если сообщение неактивно, INUSE, если
сообщение выведено на экран.
// . . .
public:
void set_status(int state);
friend int C1::idle(C2 b); // функция-"друг"
};
void C1::set_status(int state)
{
status = state;
}
void C2::set_status(int state)
{
status = state;
}
// Функция idle() -- член класса С1 и "друг" класса С2.
int C1::idle(С2 b)
{
if(status || b.status) return 0;
else return 1;
}
int main()
{
C1 x;
C2 y;
x.set_status(IDLE);
y.set_status(IDLE);
if(x.idle(y)) cout << "Экран свободен.\n";
else cout << "Отображается сообщение.\n";
x.set_status(INUSE);
if(x.idle(y)) cout << "Экран свободен.\n";
else cout << "Отображается сообщение.\n";
return 0;
}
Поскольку функция idle() является членом класса C1, она имеет прямой доступ к переменной status объектов типа С1. Следовательно, в качестве параметра необходимо передавать функции idle() только объекты типа С2.