13. 15.2. Друзья

We use cookies. Read the Privacy and Cookie Policy

13. 15.2. Друзья

Рассмотрим еще раз перегруженные операторы равенства для класса String, определенные в области видимости пространства имен. Оператор равенства для двух объектов String выглядит следующим образом:

bool operator==( const String &str1, const String &str2 )

{

if ( str1.size() != str2.size() )

return false;

return strcmp( str1.c_str(), str2.c_str() ) ? false : true;

}

Сравните это определение с определением того же оператора как функции-члена:

bool String::operator==( const String &rhs ) const

{

if ( _size != rhs._size )

return false;

return strcmp( _string, rhs._string ) ? false : true;

}

Нам пришлось модифицировать способ обращения к закрытым членам класса String. Поскольку новый оператор равенства – это глобальная функция, а не функция-член, у него нет доступа к закрытым членам класса String. Для получения размера объекта String и лежащей в его основе C-строки символов используются функции-члены size() и c_str().

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

Объявление друга (оно начинается с ключевого слова friend) встречается только внутри определения класса. Поскольку друзья не являются членами класса, объявляющего дружественные отношения, то безразлично, в какой из секций – public, private или protected – они объявлены. В примере ниже мы решили поместить все подобные объявления сразу после заголовка класса:

class String {

friend bool operator==( const String &, const String & );

friend bool operator==( const char *, const String & );

friend bool operator==( const String &, const char * );

public:

// ... остальная часть класса String

};

В этих трех строчках три перегруженных оператора сравнения, принадлежащие глобальной области видимости, объявляются друзьями класса String, а следовательно, в их определениях можно напрямую обращаться к закрытым членам данного класса:

// дружественные операторы напрямую обращаются к закрытым членам

// класса String

bool operator==( const String &str1, const String &str2 )

{

if ( str1._size != str2._size )

return false;

return strcmp( str1._string, str2._string ) ? false : true;

}

inline bool operator==( const String &str, const char *s )

{

return strcmp( str._string, s ) ? false : true;

}

// и т.д.

Можно возразить, что в данном случае прямой доступ к членам _size и _string необязателен, так как встроенные функции c_str() и size() столь же эффективны и при этом сохраняют инкапсуляцию, а значит, нет особой нужды объявлять операторы равенства для класса String его друзьями.

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

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

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

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

extern ostream& storeOn( ostream &, Screen & );

extern BitMap& storeOn( BitMap &, Screen & );

// ...

class Screen

{

friend ostream& storeOn( ostream &, Screen & );

friend BitMap& storeOn( BitMap &, Screen & );

// ...

};

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

Объявление функции другом двух классов должно выглядеть так:

class Window; // это всего лишь объявление

class Screen {

friend bool is_equal( Screen &, Window & );

// ...

};

class Window {

friend bool is_equal( Screen &, Window & );

// ...

};

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

class Window;

class Screen {

// copy() - член класса Screen

Screen& copy( Window & );

// ...

};

class Window {

// Screen::copy() - друг класса Window

friend Screen& Screen::copy( Window & );

// ...

};

Screen& Screen::copy( Window & ) { /* ... */ }

Функция-член одного класса не может быть объявлена другом второго, пока компилятор не увидел определения ее собственного класса. Это не всегда возможно. Предположим, что Screen должен объявить некоторые функции-члены Window своими друзьями, а Window – объявить таким же образом некоторые функции-члена Screen. В таком случае весь класс Window объявляется другом Screen:

class Window;

class Screen {

friend class Window;

// ...

};

К закрытым членам класса Screen теперь можно обращаться из любой функции-члена Window.

Упражнение 15.6

Реализуйте операторы ввода и вывода, определенные для класса Screen в упражнении 15.5, в виде друзей и модифицируйте их определения так, чтобы они напрямую обращались к закрытым членам. Какая реализация лучше? Объясните почему.