7.3.4. Снова о дружественных отношениях

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

Дружественные отношения между классами

В качестве примера дружественных классов рассмотрим класс Window_mgr (см. раздел 7.3.1), его членам понадобится доступ к внутренним данным объектов класса Screen, которыми они управляют. Предположим, например, что в класс Window_mgr необходимо добавить функцию-член clear(), заполняющую содержимое определенного окна пробелами. Для этого функции clear() нужен доступ к закрытым переменным-членам класса Screen. Для этого класс Screen должен объявить класс Window_mgr дружественным:

class Screen {

 // члены класса Window_Mgr смогут обращаться к закрытым

 // членам класса Screen

 friend class Window_mgr;

 // ... остальное, как раньше в классе Screen

};

Функции-члены дружественного класса могут обращаться ко всем членам класса, объявившего его другом, включая не открытые члены. Теперь, когда класс Window_mgr является другом класса Screen, функцию-член clear() класса Window_mgr можно переписать следующим образом:

class Window_mgr {

public:

 // идентификатор области для каждого окна на экране

 using ScreenIndex = std::vector<Screen>::size_type;

 // сбросить данное окно, заполнив его пробелами

 void clear(ScreenIndex);

private:

 std::vector<Screen> screens{Screen(24, 80, ' ')};

};

void Window_mgr::clear(ScreenIndex i) {

 // s - ссылка на окно, которое предстоит очистить

 Screen &s = screens[i];

 // сбросить данное окно, заполнив его пробелами

 s.contents = string(s.height * s.width, ' ');

}

Сначала определим s как ссылку на класс Screen в позиции i вектора окон. Затем переменные-члены height и width данного объекта класса Screen используются для вычисления количества символов новой строки, содержащей пробелы. Эта заполненная пробелами строка присваивается переменной-члену contents.

Если бы функция clear() не была дружественной классу Screen, то этот код не компилировался бы. Функция clear() не смогла бы использовать переменные-члены height, width или contents класса Screen. Поскольку класс Screen установил дружественные отношения с классом Window_mgr, для его функций доступны все члены класса Screen.

Важно понимать, что дружественные отношения не передаются. Таким образом, если у класса Window_mgr есть собственные друзья, то у них нет привилегий доступа к членам класса Screen.

Каждый класс сам контролирует, какие классы или функции будут его друзьями.

Как сделать функцию-член дружественной

Вместо того чтобы делать весь класс Window_mgr дружественным классу Screen, можно предоставить доступ только функции-члену clear(). Когда функция-член объявляется дружественной, следует указать класс, которому она принадлежит:

class Screen {

 // класс Window_mgr::clear должен быть объявлен перед классом Screen

 friend void Window_mgr::clear(ScreenIndex);

 // ... остальное как раньше в классе Screen

};

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

• Сначала определите класс Window_mgr, который объявляет, но не может определить функцию clear(). Класс Screen должен быть объявлен до того, как функция clear() сможет использовать члены класса Screen.

• Затем определите класс Screen, включая объявление функции clear() дружественной.

• И наконец, определите функцию clear(), способную теперь обращаться к членам класса Screen.

Перегруженные функции и дружественные отношения

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

// перегруженные функции storeOn

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

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

class Screen {

 // версия ostream функции storeOn может обращаться к закрытым членам

 // объектов класса Screen

 friend std::ostream& storeOn(std::ostream &, Screen &); // ...

};

Класс Screen объявляет другом версию функции storeOn, получающей поток ostream&. Версия, получающая параметр BitMap&, особых прав доступа к объектам класса Screen не имеет.

Объявление дружественных отношений и область видимости

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

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

struct X {

 friend void f() { /* дружественная функция может быть определена

                      в теле класса */ }

 X() { f(); } // ошибка: нет объявления для f

 void g();

 void h();

};

void X::g() { return f(); } // ошибка: f не была объявлена

void f();                   // объявляет функцию, определенную в X

void X::h() { return f(); } // ok: объявление f теперь в области

                            // видимости

Важно понимать, что объявление дружественной затрагивает доступ, но не является объявлением в обычном смысле.

Помните: некоторые компиляторы не выполняют правил поиска имен друзей (см. раздел 7.2.1).

Упражнения раздела 7.3.4

Упражнение 7.32. Определите собственные версии классов Screen и Window_mgr, в которых функция clear() является членом класса Window_mgr и другом класса Screen.

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК