Друзья класса
Пример с ошибками
Примеры
void print(int);
void print(const char *);
void print(double);
void print(long);
void print(char);
char c; int i; short s; float f;
print(c); // правило 1; вызывается print(char)
print(i); // правило 1; вызывается print(int)
print(s); // правило 2; вызывается print(int)
print(f); // правило 2; вызывается print(double)
print('a'); // правило 1; вызывается print(char)
print(49); // правило 1; вызывается print(int)
print("a"); // правило 1; вызывается print(const char *)
void f(int, float);
void f(float, int);
Вызов, который приведет к генерации сообщения об ошибке (неоднозначный выбор):
f(1.5, 1.5);
Назначение методов – создание интерфейса между внешним миром и закрытой (защищенной) частями класса. Еще один способ получить доступ к закрытой части класса – использование внешних функций, объявленных как друг класса. Функции-друзья, как и члены, являются интерфейсом класса.
Функция становится другом после ее объявления в классе с использованием спецификатора friend, например:
Определение | Реализация |
а) глобальная функция class X{ ... friend void f(); public: void fx(); ... } | void f() { ... } |
б) функция – член класса class Y{ ... friend void X::fx(); ... }; | void X::fx() { ... } |
в) класс class Z{ ... friend class Y; ... }; |
Другом класса могут быть: глобальная функция (см. примеры, приведенные выше); функция-член другого класса; все функции-члены другого класса, т.е. сам класс (при этом функции-друзья одного класса не становятся автоматически друзьями другого класса). Почти любой метод может быть сделан другом. Исключения: конструкторы, деструктор, кое-что еще.
|
|
Расположение объявления friend в определении класса (в открытой, закрытой или защищенной частях класса) роли не играют – функция-друг класса является внешней по отношению к этому классу. По этой же причине функция-друг класса, не являясь методом класса, не имеет адресата и, следовательно, не имеет указателя this (все аргументы в функцию передаются через список параметров).
Различия между членами и друзьями класса:
Функция-член | Функция-друг |
class Rational{ public: void print(); ... }; void Rational::print() { cout << num; if(den!= 1) cout << '/' << den; } ... Rational x(1,5); x.print(); | class Rational{ public: friend void print(Rational r); ... }; void print(Rational r) { cout << r.num; if(r.den!= 1) cout << '/' << r.den; } ... Rational x(1,5); print(x); |
Перегруженные операторы – друзья класса:
Бинарный оператор | Унарный оператор (префиксный и постфиксный) |
Объявление | |
friend тип operator знак_оп (op1, op2) | friend тип operator знак_оп (op1) friend тип operator знак_оп (op1, int) |
Реализация | |
тип operator знак_оп (тип op1, тип op2) {... } | тип operator знак_оп (тип op1) {... } тип operator знак_оп (тип op1, int) {... } |
Использование | |
op1 знак_оп op2 эквивалентно: operator знак_оп (op1, op2) | знак_оп op1 op1 знак_оп |
Пример перегрузки оператора записи в поток для класса Rational:
|
|
class Rational {
friend ostream& operator <<(ostream&, Rational);
...
};
ostream& operator <<(ostream& os, Rational r)
{
os << r.num;
if(r.den == 1)
os << ‘/’ << r.den;
return os;
}
Любой метод, за некоторыми (небольшими) исключениями (к ним относятся конструкторы, деструктор и кое-что еще), представляющий интерфейс класса, может быть реализован и функцией-членом, и функцией-другом класса.
Общее:
- имеют доступ к закрытой части класса,
- хотя бы один аргумент – экземпляр класса
Различие:
член | друг |
- из n параметров один (первый) параметр неявный, остальные – в списке параметров | - все n параметров в списке параметров |
- неявный параметр – адресат сообщения; доступен через this | - все параметры равноправны; адресата сообщения нет; this не определено |
- адресат сообщения (первый аргумент) – обязательно экземпляр класса | - порядок и типы аргументов определяются объявлением функции |
Rational x(1,3); x + 1 - все в порядке 1 + x - ошибка! | Rational x(1,3); x + 1 - все в порядке 1 + x - все в порядке |
Функции-члены класса:
- конструкторы, деструкторы, виртуальные функции;
- операции, требующие в качестве операндов основных типов lvalue (например, =, +=, ++ и т.д.)
- операции, изменяющие состояние объекта
Функции-друзья класса:
- операции, требующие неявного преобразования операндов (например, +, - и т.д.)
- операции, первый операнд которых не соответствует типу экземпляра класса (например, << и >>).
При прочих равных условиях лучше выбирать функции-члены класса.