Дружественные классы и итераторы
Класс может определять объекты, которые необходимы в качестве внутренних деталей для других классов. Чтобы сохранить свою анонимную роль, такой класс должен быть закрытым. Класс, управляющий этими закрытыми объектами, должен быть в дружественных отношениях с ними.
Вспомним тип my_string, определенный с семантикой подсчета ссылок из раздела 6.10, «Строки, использующие семантику ссылок», на стр. 197. Отдельные значения my_string были в конечном счете ссылочными объектами типа str_obj. Это разъединение позволяло одному экземпляру str_obj, которому, возможно, требовалось много байт для хранения, быть использованным многими экземплярами my_string. Можно передать этот пример, используя дружественные отношения для сохранения закрытости класса str_obj.
В файле string8.H
class str_obj {
private:
friend class my_string;
friend class string_iterator;
friend ostream&
operator << (ostream& out, const my_string& str);
int len, ref_cnt;
char* s;
str_obj ( ) : len (0), ref_cnt (1) { s = new char [1];
str_obj (int n) : len (n), ref_cnt (1)
{ s = new char [n + 1]; }
str_obj (const char* p) : ref_cnt (1)
{ len = strlen (p); s = new char [len + 1];
strcpy (s, p); }
~ str_obj ( ) { delete [ ] s; }
};
Этот проект с использованием двух классов имеет преимущество – большую гибкость за счет дальнейшего разделения деталей реализации и клиентского кода.
Итератором также обычно необходимы дружественные отношения с объектом, который они перебирают. добавим класс-итератор и изменим наш my_string, чтобы перегрузить операторы присваивания и «помещения в», расширив таким образом наш пример.
В файле string8.H
class my_string {
public:
my_string ( ) { st = new str_obj; }
my_string ( int n ) { st = new str_obj (n); }
my_string (const char* p) { st = new str_obj (p); }
my_string (const my_string& str)
{ st = str . st; st -> ref_cnt++; }
~ my_string ( );
void assign (const my_string& str);
void print ( ) const { cout << st -> s ; }
my_string& operator= (my_string& str)
{ assign (str); return str; }
friend class string_iterator;
friend ostream&
operator << (ostream& out, const my_string& str);
private:
str_obj* st;
};
// дружественная функция класса my_string
// «поместить в» - типичный способ перегрузки <<
// Так как нужен синтаксис
// ostream_переменная << переменная_типа,
// функция-член класса my_string недопустима.
ostream& operator<< (ostream& out, const my_string& str)
{
out << str. st -> s;
return out;
}
Как было сказано, ostream& operator<< (ostream&, const my_string&) нуждается в дружественных отношениях с str_obj. Эта функция использует закрытые детали реализации класса my_string. Она не может быть записана как функция-член, так как ее первым аргументом должен быть ostream. Поскольку возвращаемое ею значение имеет тип ostream&, она может быть использована для многократной подстановки в выражение. Данное употребление соответствует соглашениям iostream.h.
Функция-член assign ( ) использовались нами для кодирования operator = ( ).
Чтобы позволить множественные присваивания возвращается ссылочное значение. Стоит отметить, что если бы присваивание не было перегружено, обычная семантика присвоения не работала бы – подсчет ссылок не выполнялся бы надлежащим образом.
Соответствующий класс-итератор следует общей схеме. у нас есть конструктор, связывающий объект итератора с перебираемым объектом с помощью инициализации my_string* ptr_s. Мы предусматриваем закрытую позиционную переменную cur_ind. Поскольку дружественные отношения между my_string и str_obj не транзитивны, для нашей схемы необходимо, чтобы и string_iterator был дружественен str-obj.