Что значит дружественные классы

friend (C++)

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

Синтаксис

Объявления дружественных элементов

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

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

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

В предыдущем примере в области действия вводится имя класса ForwardDeclared , но полное объявление — в частности, часть, в которой объявляется функция IsAFriend , — отсутствует. Поэтому friend объявление в классе HasFriends создает ошибку.

Начиная с C++ 11, существует две формы дружественных объявлений для класса:

Первая форма представляет новый класс F, если в самом внутреннем пространстве имен не найден существующий класс с таким именем. C++ 11. во второй форме не представлен новый класс. его можно использовать, если класс уже объявлен и должен использоваться при объявлении параметра типа шаблона или typedef как дружественного.

Читайте также:  Что значит свиная рулька

Используется friend class F , если тип, на который указывает ссылка, еще не объявлен:

В следующем примере friend F ссылается на F класс, объявленный вне области видимости NS.

Используйте friend F для объявления параметра шаблона в качестве дружественного:

Используйте friend F , чтобы объявить typedef как Friend:

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

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

дружественные функции

friend Функция — это функция, которая не является членом класса, но имеет доступ к закрытым и защищенным членам класса. Дружественные функции не считаются членами класса; это обычные внешние функции с особыми правами доступа. Друзья не находятся в области класса и не вызываются с помощью операторов выбора членов (. и- ), если они не являются членами другого класса. friend Функция объявляется классом, который предоставляет доступ. friend Объявление можно поместить в любое место объявления класса. На него не влияют ключевые слова управления доступом.

В следующем примере показан класс Point и дружественная функция ChangePrivate . friend Функция имеет доступ к закрытым членам данных объекта, который Point он получает в качестве параметра.

Члены класса как дружественные элементы

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

В предыдущем примере дружественный доступ к классу A::Func1( B& ) предоставляется только функции B . Таким образом, доступ к закрытому члену _b будет правильным в Func1 классе, A но не в Func2 .

Класс friend — это класс, все функций-члены которого являются дружественными функциями класса, то есть функции-члены которого имеют доступ к закрытым и защищенным членам другого класса. Предположим, что в классе friend было следующее объявление B :

В этом случае все функции-члены из класса A имели бы дружественный доступ к классу B . В следующем коде приведен пример дружественного класса.

Дружественные отношения не являются взаимными, если это не указано явным образом. В предыдущем примере функции-члены класса YourClass не имеют доступа к закрытым членам класса YourOtherClass .

Управляемый тип (в C++/CLI) не может иметь дружественных функций, дружественных классов или дружественных интерфейсов.

Дружественные отношения не наследуются; это означает, что классы, производные от YourOtherClass , не могут обращаться к закрытым членам класса YourClass . Дружественные отношения не являются переходящими, поэтому классы, дружественные классу YourOtherClass , не могут обращаться к закрытым членам класса YourClass .

На следующем рисунке показаны объявления 4 классов: Base , Derived , aFriend и anotherFriend . Только класс aFriend имеет прямой доступ к закрытым членам класса Base (и к любым возможным унаследованным членам класса Base ).


Последствия дружественных отношений

Встроенные определения дружественных элементов

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

Источник

12.15 – Дружественные функции и классы

Большую часть этой главы мы проповедовали достоинства сохранения скрытости ваших данных. Однако иногда вы можете столкнуться с ситуациями, когда обнаружите, что у вас есть классы и функции вне этих классов, которые должны очень тесно работать друг с другом. Например, у вас может быть класс, в котором хранятся данные, и функция (или другой класс), которая отображает эти данные на экране. Хотя класс хранилища и код отображения были разделены для упрощения поддержки, код отображения на самом деле тесно связан с деталями класса хранилища. Следовательно, скрытие сведений о классах хранения от кода отображения не дает особой выгоды.

В подобных ситуациях есть два варианта:

  1. Использовать в коде отображения открытые функции класса хранилища. Однако у этого есть несколько потенциальных недостатков. Во-первых, эти открытые функции-члены должны быть определены, что требует времени и может загромождать интерфейс класса хранилища. Во-вторых, классу хранилища, возможно, придется предоставить для кода отображения функции, которые он не хочет делать доступными для кого-либо еще. Но невозможно сказать «эта функция предназначена для использования только классом отображения».
  2. В качестве альтернативы, используя дружественные классы и дружественные функции, вы можете предоставить своему коду отображения доступ к закрытым деталям класса хранилища. Это позволяет коду отображения напрямую обращаться ко всем закрытым членам и функциям класса хранилища, не давая при этом доступ кому-либо еще! В этом уроке мы подробнее рассмотрим, как это делается.

Дружественные функции

Дружественная функция – это функция, которая может получить доступ к закрытым членам класса, как если бы она была членом этого класса. Во всем остальном дружественная функция похожа на обычную функцию. Дружественная функция может быть либо обычной функцией, либо функцией-членом другого класса. Чтобы объявить дружественную функцию, просто используйте ключевое слово friend перед прототипом функции, которую вы хотите сделать другом класса. Не имеет значения, объявляете ли вы дружественную функцию в закрытом или открытом разделе класса.

Вот пример использования дружественной функции:

В этом примере мы объявили функцию с именем reset() , которая принимает объект класса Accumulator и устанавливает значение m_value равным 0. Поскольку reset() не является членом класса Accumulator , обычно reset() не будет иметь доступ к закрытым членам Accumulator . Однако, поскольку Accumulator специально объявил эту функцию reset() как друга класса, то ей предоставляется доступ к закрытым членам Accumulator .

Обратите внимание, что мы должны передать в reset() объект Accumulator . Это потому, что reset() не является функцией-членом. У нее нет указателя *this и нет объекта Accumulator для работы, если он не указан.

Вот еще один пример:

В этом примере мы объявляем функцию isEqual() другом класса Value . isEqual() принимает в качестве параметров два объекта Value . Поскольку isEqual() является другом класса Value , она может получить доступ к закрытым членам всех объектов Value . В этом случае она использует этот доступ для сравнения двух объектов и возвращает true , если они равны.

Хотя оба приведенных выше примера довольно надуманы, последний пример очень похож на случаи, с которыми мы столкнемся позже, когда будем обсуждать перегрузку операторов!

Несколько друзей

Функция может быть другом для более чем одного класса одновременно. Например, рассмотрим следующий пример:

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

Это прототип класса, который сообщает компилятору, что в будущем мы собираемся определить класс под названием Humidity . Без этой строки компилятор при синтаксическом анализе прототипа для printWeather() внутри класса Temperature сообщил бы нам, что не знает, что такое Humidity . Прототипы классов выполняют ту же роль, что и прототипы функций – они сообщают компилятору, как что-то выглядит, чтобы его можно было использовать сейчас и определить позже. Однако, в отличие от функций, классы не имеют возвращаемых типов или параметров, поэтому прототипы классов всегда представляют собой просто class ClassName , где ClassName – это имя класса.

Дружественные классы

Также целый класс можно сделать другом другого класса. Это дает всем членам дружественного класса доступ к закрытым членам другого класса. Вот пример:

Поскольку класс Display является другом Storage , любой из членов Display , использующих объект класса Storage , может напрямую обращаться к закрытым членам Storage . Эта программа дает следующий результат:

Несколько дополнительных замечаний о дружественных классах. Во-первых, даже несмотря на то, что Display является другом Storage , Display не имеет прямого доступа к указателю *this объектов Storage . Во-вторых, то, что Display является другом Storage , не означает, что Storage также является другом Display . Если вы хотите, чтобы два класса дружили друг с другом, они оба должны объявить друг друга друзьями. Наконец, если класс A является другом B , а B – другом C , это не означает, что A является другом C .

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

Дружественные функции-члены

Вместо того чтобы делать другом весь класс, вы можете сделать другом только одну функцию-член. Это делается аналогично тому, как сделать дружественной обычную функцию, за исключением использования имени функции-члена с включенным префиксом ИмяКласса:: (например, Display::displayItem ).

Однако на самом деле это может быть немного сложнее, чем ожидалось. Давайте, преобразуем предыдущий пример, чтобы сделать Display::displayItem дружественной функцией-членом. Вы можете попробовать сделать что-то вроде этого:

Однако оказывается, что это не сработает. Чтобы сделать функцию-член другом, компилятор должен увидеть полное определение класса функции-члена (а не только предварительное объявление). Поскольку класс Storage еще не видел полного определения класса Display , компилятор выдаст ошибку в тот момент, когда мы попытаемся сделать функцию-член другом.

К счастью, это легко решить, просто поставив определение класса Display перед определением класса Storage .

Однако теперь у нас есть другая проблема. Поскольку функция-член Display::displayItem() использует Storage в качестве ссылочного параметра, а мы только что переместили определение Storage ниже определения Display , компилятор будет жаловаться, что не знает, что такое Storage . Мы не можем исправить это, изменив порядок определения, потому что тогда мы отменим предыдущее исправление.

К счастью, это тоже можно исправить, выполнив пару простых шагов. Во-первых, мы можем добавить класс Storage в качестве предварительного объявления. Во-вторых, мы можем переместить определение Display::displayItem() из класса на место после полного определения класса Storage .

Вот как это выглядит:

Теперь всё будет скомпилировано правильно: предварительного объявления класса Storage достаточно, чтобы удовлетворить объявление Display::displayItem() , полное определение Display удовлетворяет объявлению Display::displayItem() как друга Storage , а полное определение класса Storage достаточно, чтобы удовлетворить определение функции-члена Display::displayItem() . Если это немного сбивает с толку, смотрите комментарии в программе.

Если это похоже на боль – это так. К счастью, этот танец с бубном необходим только потому, что мы пытаемся сделать всё в одном файле. Лучшее решение – поместить определение каждого класса в отдельный заголовочный файл, а определения функций-членов в соответствующие файлы .cpp . Таким образом, все определения классов сразу же были бы видны в файлах .cpp , и не нужно было бы переупорядочивать классы и функции!

Резюме

Дружественная функция или класс – это функция или класс, которые могут получить доступ к закрытым членам другого класса, как если бы они были членами этого класса. Это позволяет дружественной функции или классу тесно работать с другим классом, не заставляя другой класс открывать свои закрытые члены (например, через функции доступа).

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

Обратите внимание, что для того, чтобы сделать конкретную функцию-член другом, необходимо сначала увидеть полное определение класса функции-члена.

Небольшой тест

Вопрос 1

В геометрии точка – это позиция в пространстве. Мы можем определить точку в трехмерном пространстве как набор координат x, y и z. Например, Point(2.0, 1.0, 0.0) будет точкой в ​​пространстве с координатами x = 2.0, y = 1.0 и z = 0.0.

В физике вектор – это величина, которая имеет величину (длину) и направление (но не положение). Мы можем определить вектор в трехмерном пространстве как значения x, y и z, представляющие направление вектора вдоль осей x, y и z (длина может быть получена из них). Например, Vector(2.0, 0.0, 0.0) будет вектором, представляющим направление вдоль (только) положительной оси x, с длиной 2.0.

Вектор можно применить к точке, чтобы переместить точку в новое положение. Это делается путем добавления направления вектора к положению точки, чтобы получить новое положение. Например, Point(2.0, 1.0, 0.0) + Vector(2.0, 0.0, 0.0) даст точку (4.0, 1.0, 0.0).

Точки и векторы часто используются в компьютерной графике (точки представляют вершины фигуры, а векторы представляют движение фигуры).

Учитывая следующую программу:

1a) Сделайте Point3d дружественным классом класса Vector3d и реализуйте функцию Point3d::moveByVector() .

1b) Вместо того, чтобы делать класс Point3d другом класса Vector3d , сделайте функцию-член Point3d::moveByVector другом класса Vector3d .

Источник

Оцените статью