C++, реальные примеры, когда friend классы и функции были бы уместнее, чем иное решение?
Я задумался над целесообразностью использования в C++ дружественных функций и классов. Пытался найти внятные примеры, когда это действительно необходимо и дает плюсов больше, чем минусов. Таких примеров мне удалось найти совсем немного.
Может быть, у кого-нибудь есть примеры из жизни, когда friend классы и функции были очень удобны?
Ответы (3 шт):
В С++ модификаторы доступа одинаково работают для всех пользователей класса. Использование friend по сути просто делает все кишки класса доступными для дружественной сущности, даже если ей нужно только несколько, и поэтому обычно считается сомнительной практикой. Возможность дать доступ к одной части членов класса для одних пользователей, к другой - для других, а к третьей - третьим (как это делается в языке Еiffel например) из коробки отсутствует. Однако такой высокогранулированный доступ можно обеспечить используя идиому Attorney-Client:
class Client
{
friend class AttorneyAB;
private: void A(int a);
private: void B(float b);
private: void C(double c);
};
class AttorneyAB
{
friend class User;
private: static void callA(Client & c, int a) { c.A(a); }
private: static void callB(Client & c, float b) { c.B(b); }
};
class User
{
// User now has access to only Client::A and Client::B through the Attorney.
};
Стандартная библиотека часто для реализации той или иной сущности, использует дружественные классы. Например:
- класс локализации
localeопределяет классfacet, деструктор которого защищенный, чтобы все фасеты, наследованные отlocale::facetи объявляющие классlocaleсвоим другом, могли быть уничтожены только данной локализацией. - Класс
std::valarrayявляется другом классов,std::slice_array,std::gslice_array, std::mask_array, std::indirect_array, чьи конструкторы являются закрытыми, дабы их экземпляры могли создаваться только обьектамиstd::valarray. (Эти классы являются вспомогательными для std::valarray, например для разного представления возвращаемого типаoperator[]).
Для того, чтобы существующая функция(класс) могла пользоваться закрытыми членами(в том числе, создать или уничтожать обьект, когда конструктор и деструктор не имеют публичный спецификатор доступа), нужно, чтобы она была другом класса...
Начну немного издалека. Вы пишете шаблонный класс имеющий несколько параметров:
template <typename T1, typename T2, int sz> class MyCoolClass {
};
Теперь для этого класса нужно перегрузить операторы +,-,*,/. Только, как то не хочется отдельно перегружать для каждого типа, давайте сделаем перегрузку шаблонной. Я опущу замещения для разных типов:
template <typename T1, typename T2, int sz> class MyCoolClass {
public:
template <class Ti> MyCoolClass operator + (const Ti v) const
{
// ....
}
// Остальные -,*,/
// ...
};
Все пока хорошо. Выражения типа MyCoolClass a = MyCoolClass() + 5 работают замечательно. Но, вот выражения MyCoolClass a = 5 + MyCoolClass() не работают.
Ладно, значит надо перегрузить операторы глобально. Вот только теперь написать шаблонную перегрузку будет не очень удобно, даже пример приводить неохота. И вот в этом случае дружественная шаблонная функция просто спасение:
template <typename T1, typename T2, int sz> class MyCoolClass {
public:
template <class Ti> MyCoolClass operator + (const Ti v) const
{
// ....
}
// Остальные -,*,/
// ...
template <class Ti> friend MyCoolClass operator + (const Ti v, MyCoolClass& v2)
{
// ....
}
};
Обратите внимание, параметры шаблонного класса указывать просто не нужно, что значительно сокращает и упрощает подобные функции. Поэтому в данном случае дружественные функции однозначно полезны. И хотя это только один пример, мне кажется он достаточно веский для существования такого механизма.