В классе закрыто поле, а внешняя функция должна обращаться к нему без публичного метода. Как механизм friend меняет проверку доступа и остаётся ли такая функция членом класса?
Объявление функции как friend разрешает ей обращаться к закрытым и защищённым членам указанного класса. При этом функция не становится членом класса: у неё нет неявного параметра this, она не участвует в наследовании и не вызывается с синтаксисом метода.
Дружественные функции нужны для случаев, когда операция логически является внешней функцией, но должна работать с внутренним представлением класса. Это особенно полезно для симметричных операторов, потокового ввода-вывода и фабрик, которым неудобно или неправильно становиться методами класса.
Подход позволяет сохранить закрытое состояние объекта для обычного кода и одновременно явно перечислить доверенные внешние сущности. Доступ предоставляется точечно, через декларацию дружбы, а не всему пространству имён или модулю.
Если внешняя функция обращается к закрытому полю без специального разрешения, программа не компилируется. Переносить такую операцию в публичный метод не всегда желательно: это может расширить интерфейс класса и раскрыть лишнюю операцию.
Объявление friend решает проблему доступа, но создаёт тесную связь между функцией и внутренним устройством класса. Изменение закрытых полей может потребовать изменения дружественной функции, поэтому дружбу не следует использовать как универсальную замену инкапсуляции.
Дружественная функция проверяется компилятором так, как будто ей разрешён доступ к private и protected членам класса. Это разрешение относится только к конкретной функции или классу, указанным в объявлении дружбы.
Дружественная функция не является членом класса. Она не получает this, не может быть виртуальной функцией класса и не наследует свойства методов. Вызов обычно выполняется как вызов свободной функции.
Здесь print_balance имеет доступ к balance, но Account не содержит метода print_balance. Дружба является односторонней и не распространяется автоматически: если Account дружит с Inspector, это не делает Inspector другом другого класса. Дружба также не наследуется производными классами.
Дружественной может быть функция, другой класс или конкретный метод другого класса. Предоставлять дружбу всему классу следует осторожно: это даёт доступ всем его методам, хотя реально он может требоваться только одному.
У дружественной функции, определённой прямо внутри класса, есть дополнительная особенность: она является функцией внешнего пространства имён, а не членом класса; такое определение не превращает её в метод. При проектировании интерфейса важно также учитывать поиск имён и возможное обнаружение такой функции через аргумент-зависимый поиск.
Допустим, класс Matrix хранит элементы в закрытом буфере, а операция умножения должна быть внешней функцией. Публичные методы доступа к каждому элементу упростили бы реализацию умножения, но увеличили бы открытый интерфейс и могли бы ухудшить контроль инвариантов.
Варианты решения:
Если операция действительно является частью логики абстракции, а не просто способом обойти инкапсуляцию, разумно объявить конкретную функцию дружественной. При этом следует предоставить дружбу только ей и не использовать её для массового доступа к состоянию.
1. Может ли дружественная функция обращаться к private членам без объекта?
Нет, дружба разрешает доступ, но не создаёт объект класса и не даёт неявный this. Для обращения к нестатическому полю всё равно нужен конкретный объект, переданный явно или полученный другим способом. Дружественная функция может обращаться без объекта только к статическим членам и другим сущностям, для которых объект не требуется.
2. Передаётся ли дружба производному классу?
Нет. Если функция объявлена другом базового класса, она получает доступ к закрытым членам базового класса, но это не делает её автоматически другом производного класса. Закрытые члены производного класса требуют отдельного разрешения; защищённые члены регулируются собственными правилами доступа.
3. Можно ли сделать дружественную функцию виртуальной?
Нет. Виртуальность относится к нестатическим функциям-членам, для которых существует объект и неявный параметр this. Свободная функция, в том числе дружественная, не является членом класса, поэтому динамический полиморфизм через таблицу виртуальных функций к ней неприменим.