Зачем нужны друзья класса c
Перейти к содержимому

Зачем нужны друзья класса c

  • автор:

Дружественные функции — зачем они нужны?

Только что с ними разобрался, но не вижу их применения. Где их применяют?

Отслеживать

11 1 1 золотой знак 2 2 серебряных знака 8 8 бронзовых знаков

задан 11 ноя 2014 в 10:32

10k 15 15 золотых знаков 53 53 серебряных знака 119 119 бронзовых знаков

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Дружественные функции чаще всего не нужны и даже вредны, т. к. они разрушают инкапсуляцию. Однако иногда всё-таки без нарушения инкапсуляции трудно.

Классический пример — бинарный оператор. Хорошим тоном является определение бинарного оператора как внешней статической функции. Для того, чтобы эффективнее вычислять значение, оператору может понадобиться доступ «внутрь» класса, так что его приходится объявлять friend ‘ом.

class YetAnotherString < char* m_data; int m_length, m_capacity; YetAnotherString(int length) < m_length = length; PrepareBuffer(); >void PrepareBuffer() < m_capacity = ((m_length + 31) / 32) * 32 // выровнять на 32 байта m_data = new char[m_capacity + 1]; >public: YetAnotherString(const char* data) < m_length = strlen(data); PrepareBuffer(); strcpy(m_data, data); >int GetLength() < return m_length; >char* GetCStr() < return m_data; >// тут ещё оператор присваивания, копирующий конструктор // и деструктор friend operator + (char* raw, const YetAnotherString& string); >; YetAnotherString operator + (char* raw, const YetAnotherString& string) < int lhsLength = strlen(raw); int rhsLength = string.GetLength(); // мы хотим избежать ненужного копирования в конструкторе YetAnotherString // поэтому пользуемся приватным конструктором // без friend-декларации это бы не скомпилировалось YetAnotherString result(lhsLength + rhsLength); strcpy(result.m_data, raw); strcpy(result.m_data + lhsLength, string.GetCStr()); return result; >

Друзья / FAQ C++

Это то, что позволяет вашему классу предоставлять доступ к себе другому классу или функции.

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

Нарушают ли друзья инкапсуляцию?

Нет! При правильном использовании они улучшают инкапсуляцию.

«Друг» – это явный механизм предоставления доступа, как и членство. Вы не можете (в стандартной программе) предоставить себе доступ к классу без изменения его источника. Например:

class X < int i; public: void m(); // предоставляет доступ X::m() friend void f(X&); // предоставляет доступ f(X&) // . >; void X::m() < i++; /* X::m() может получить доступ к X::i */ >void f(X& x) < x.i++; /* f(X&) может получить доступ к X::i */ >

Описание модели защиты C++ смотрите в D&E (раздел 2.10) и TC++PL (разделы 11.5, 15.3 и C.11).

Часто бывает необходимо разделить класс пополам, если у этих двух половин будет разное количество экземпляров или разное время жизни. В этих случаях двум половинкам обычно требуется прямой доступ друг к другу (две половины раньше находились в одном классе, поэтому вы не увеличили объем кода, которому требуется прямой доступ к структуре данных; вы просто перетасовали код на два класса вместо одного). Самый безопасный способ реализовать это – подружить эти две половинки.

Если вы используете друзей, только как описано выше, то, что было private , так и останется private . Люди, которые этого не понимают, часто прилагают наивные усилия, чтобы избежать использования дружбы в ситуациях, подобных описанным выше, и часто фактически разрушают инкапсуляцию. Они либо используют публичные данные (абсурд!), либо делают данные доступными между этими половинами через публичные функции-члены get() и set() . Наличие публичных функций-членов get() и set() для частных данных – это нормально, только когда эти частные данные «имеют смысл» извне класса (с точки зрения пользователя). Во многих случаях эти функции-члены get() / set() почти так же плохи, как и общедоступные данные: они скрывают (только) имя частных данных, но не скрывают само существование частных данных.

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

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

Какие преимущества/недостатки есть у использования дружественных функций?

Они предоставляют некоторую свободу в вариантах проектирования интерфейса.

Функции-члены и дружественные функции имеют одинаковые привилегии (наделены ими на 100%). Основное отличие состоит в том, что дружественная функция вызывается как f(x) , а функция-член – как x.f() . Таким образом, возможность выбора между функциями-членами ( x.f() ) и дружественными функциями ( f(x) ) позволяет разработчику выбирать синтаксис, который считается наиболее читаемым, что снижает затраты на поддержку.

Основным недостатком дружественных функций является то, что они требуют дополнительной строки кода, когда вам нужна динамическая привязка. Чтобы получить эффект virtual friend , дружественная функция должна вызывать скрытую (обычно защищенную) виртуальную функцию-член. Это называется идиомой виртуальной дружественной функции (Virtual Friend Function Idiom). Например:

class Base < public: friend void f(Base& b); // . protected: virtual void do_f(); // . >; inline void f(Base& b) < b.do_f(); >class Derived : public Base < public: // . protected: virtual void do_f(); // "Переопределить" поведение f(Base& b) // . >; void userCode(Base& b)

Что значит «дружба не передается по наследству, не является переходящей или взаимной»?

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

  • Я не обязательно доверяю детям своих друзей. Привилегии дружбы не передаются по наследству. Производные дружественные классы – не обязательно друзья. Если класс Fred объявляет, что класс Base является другом, классы, производные от Base , не имеют никаких автоматических специальных прав доступа к объектам Fred .
  • Я не обязательно доверяю друзьям своих друзей. Привилегии дружбы не передаваемы. Друг друга – не обязательно друг. Если класс Fred объявляет своим другом класс Wilma , а класс Wilma объявляет своим другом класс Betty , класс Betty не обязательно имеет какие-либо особые права доступа к объектам Fred .
  • Вы не обязательно доверяете мне только потому, что я объявляю вас своим другом. Привилегии дружбы не взаимны. Если класс Fred объявляет, что класс Wilma является ему другом, объекты Wilma имеют особый доступ к объектам Fred , но объекты Fred не имеют автоматически особого доступа к объектам Wilma .

Что мне лучше объявлять в своем классе, функцию-член или дружественную функцию?

Используйте функцию-член, когда можете, и дружественную функцию, когда вам нужно.

Иногда друзья синтаксически лучше (например, в классе Fred дружественные функции позволяют параметру Fred быть вторым, в то время как функции-члены требуют, чтобы он был первым). Еще одно хорошее применение дружественных функций – это двоичные инфиксные арифметические операторы. Например, aComplex + aComplex должен быть определен как друг, а не как член, если вы хотите разрешить aFloat + aComplex (функции-члены не позволяют продвигать левый аргумент, так как это изменит класс объекта, который является получателем вызова функции-члена).

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

Зачем нужны друзья класса c

Дружественные функции — это функции, которые не являются членами класса, однако имеют доступ к его закрытым членам — переменным и функциям, которые имеют спецификатор private.

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

#include class Auto < friend void drive(const Auto&); friend void setPrice(Auto&, unsigned); public: Auto(std::string autoName, unsigned autoPrice) < name = autoName; price = autoPrice; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void drive(const Auto &car) < std::cout void setPrice(Auto &car, unsigned price) < car.price = price; >int main() < Auto tesla("Tesla", 5000); tesla.print(); // drive(tesla); setPrice(tesla, 8000); tesla.print(); // >

Здесь определен класс Auto, который представляет автомобиль. У этого класса определены приватные закрытые переменные name (название автомобиля) и price (цена автомобиля). Также в классе объявлены две дружественные функции: drive (функция вождения автомобиля) и setPrice (функция назначения цены). Обе этих функции принимают в качестве параметра ссылку на объект Auto.

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

При этом для дружественных функций не важно, определяются они под спецификатором public или private. Для них это не имеет значения.

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

Консольный вывод программы:

Tesla : 5000 Tesla is driven Tesla : 8000

Определение дружественных функций в классе

Дружественные функции могут определяться в другом классе. Например, определим класс Person, который использует объект Auto:

#include class Auto; // объявление класса Auto, чтобы Person видел этот класс class Person < public: Person(std::string p_name) < name = p_name; >void drive(const Auto&); void setPrice(Auto&, unsigned); private: std::string name; >; class Auto < // объявление дружественных функций friend void Person::drive(const Auto&); friend void Person::setPrice(Auto&, unsigned); public: Auto(std::string a_name, unsigned a_price) < name = a_name; price = a_price; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void Person::drive(const Auto &car) < std::cout void Person::setPrice(Auto &car, unsigned price) < car.price = price; >int main() < Auto tesla; Person tom; tom.drive(tesla); tom.setPrice(tesla, 8000); tesla.print(); >

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

Две функции из класса Person принимают ссылку на объект Auto:

void drive(const Auto&); void setPrice(Auto&, unsigned);

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

Класс Auto определяет дружественные функции с той же сигнатурой:

friend void Person::drive(Auto&); friend void Person::setPrice(Auto&, unsigned);

Причем поскольку данные функции будут определены в классе Person, то названия этих функций предваряются префиксом «Person::».

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

Консольный вывод программы:

Tom drives Tesla Tesla : 8000

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

В случае выше класс Person использует только две функции из класса Auto. Но допустим впоследствии возникла необходимость добавить в класс Auto еще ряд дружественных функций, которые будут определены в классе Person. Либо мы можем предполагать, что класс Person будет активно использовать объекты Auto. И в этом случае целесообразно определять не отдельные дружественные функции, а определить дружественным весь класс Person:

#include class Auto; // объявление класса Auto, чтобы Person видел этот класс class Person < public: Person(std::string p_name) < name = p_name; >void drive(const Auto&); void setPrice(Auto&, unsigned); private: std::string name; >; class Auto < // объявление дружественного класса friend class Person; public: Auto(std::string a_name, unsigned a_price) < name = a_name; price = a_price; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void Person::drive(const Auto& car) < std::cout void Person::setPrice(Auto& car, unsigned price) < car.price = price; >int main() < Auto tesla; Person tom; tom.drive(tesla); tom.setPrice(tesla, 8000); tesla.print(); >

Единственное, что в данном случае изменилось по сравнению с предыдущим примером — это то, что в классе Auto определение дружественных функций было заменено определением дружественного класса:

friend class Person;

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

friend (C++)

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

Синтаксис

friend-declaration :
friend function-declaration
friend function-definition
friend elaborated-type-specifier ; ;
friend simple-type-specifier ;
friend typename-specifier ;

friend Объявления

Если вы объявляете friend функцию, которая не была объявлена ранее, эта функция экспортируется в заключающую неклассовую область.

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

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

class ForwardDeclared; // Class name is known. class HasFriends < friend int ForwardDeclared::IsAFriend(); // C2039 error expected >; 

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

В C++11 существует две формы объявлений друзей для класса:

friend class F; friend F; 

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

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

namespace NS < class M < friend class F; // Introduces F but doesn't define it >; > 

Ошибка возникает, если вы используете friend с типом класса, который не был объявлен:

namespace NS < class M < friend F; // error C2433: 'NS::F': 'friend' not permitted on data declarations >; > 

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

class F <>; namespace NS < class M < friend F; // OK >; > 

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

template class my_class < friend T; //. >; 

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

class Foo <>; typedef Foo F; class G < friend F; // OK friend class F // Error C2371 -- redefinition >; 

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

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

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

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

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

// friend_functions.cpp // compile with: /EHsc #include using namespace std; class Point < friend void ChangePrivate( Point & ); public: Point( void ) : m_i(0) <>void PrintPrivate( void ) private: int m_i; >; void ChangePrivate ( Point &i ) < i.m_i++; >int main() < Point sPoint; sPoint.PrintPrivate(); ChangePrivate(sPoint); sPoint.PrintPrivate(); // Output: 0 1 >

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

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

// classes_as_friends1.cpp // compile with: /c class B; class A < public: int Func1( B& b ); private: int Func2( B& b ); >; class B < private: int _b; // A::Func1 is a friend function to class B // so A::Func1 has access to all members of B friend int A::Func1( B& ); >; int A::Func1( B& b ) < return b._b; >// OK int A::Func2( B& b ) < return b._b; >// C2248 

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

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

friend class A; 

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

// classes_as_friends2.cpp // compile with: /EHsc #include using namespace std; class YourClass < friend class YourOtherClass; // Declare a friend class public: YourClass() : topSecret(0)<>void printMember() < cout private: int topSecret; >; class YourOtherClass < public: void change( YourClass& yc, int x )>; int main()

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

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

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

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

На схеме показано, что класс anotherFriend не имеет отношения друг с базой классов, которую друзья класс aFriend. Класс aFriend друг от класса Base, но у него нет отношения друг с классом Производное, даже если класс Производно наследует от Base. Это показывает, что наследование не означает, что производный класс имеет те же друзья, что и базовый класс.

Встроенные friend определения

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *