Friend declaration
Объявление друга появляется в теле класса и предоставляет функции или другому классу доступ к приватным и защищенным членам класса, в котором появляется объявление друга.
Содержание |
Синтаксис
friend
объявление-функции
|
(1) | ||||||||
friend
определение-функции
|
(2) | ||||||||
friend
спецификатор-уточненного-типа
;
|
(3) | (до C++26) | |||||||
friend
спецификатор-простого-типа
;
|
(4) |
(начиная с C++11)
(до C++26) |
|||||||
friend
список-спецификаторов-дружественного-типа
;
|
(5) | (начиная с C++26) | |||||||
| function-declaration | - | объявление функции |
| function-definition | - | определение функции |
| elaborated-type-specifier | - | уточняющий спецификатор типа |
| simple-type-specifier | - | простой спецификатор типа |
| typename-specifier | - | ключевое слово typename за которым следует квалифицированный идентификатор или квалифицированный простой идентификатор шаблона |
| friend-type-specifier-list | - |
непустой список, разделенный запятыми, из
simple-type-specifier
,
elaborated-type-specifier
и
typename-specifier
ов, каждый спецификатор может сопровождаться многоточием (
...
)
|
Описание
class Y { int data; // private member // the non-member function operator<< will have access to Y's private members friend std::ostream& operator<<(std::ostream& out, const Y& o); friend char* X::foo(int); // members of other classes can be friends too friend X::X(char), X::~X(); // constructors and destructors can be friends }; // friend declaration does not declare a member function // this operator<< still needs to be defined, as a non-member std::ostream& operator<<(std::ostream& out, const Y& y) { return out << y.data; // can access private member Y::data }
class X { int a; friend void friend_set(X& p, int i) { p.a = i; // this is a non-member function } public: void member_set(int i) { a = i; // this is a member function } };
class Y {}; class A { int data; // private data member class B {}; // private nested type enum { a = 100 }; // private enumerator friend class X; // friend class forward declaration (elaborated class specifier) friend Y; // friend class declaration (simple type specifier) (since C++11) // the two friend declarations above can be merged since C++26: // friend class X, Y; }; class X : A::B // OK: A::B accessible to friend { A::B mx; // OK: A::B accessible to member of friend class Y { A::B my; // OK: A::B accessible to nested member of friend }; int v[A::a]; // OK: A::a accessible to member of friend };
Шаблонные друзья
Как
шаблоны функций
, так и
шаблоны классов
могут быть объявлены со спецификатором
friend
в любом нелокальном классе или шаблоне класса (хотя только шаблоны функций могут быть определены внутри класса или шаблона класса, который предоставляет дружественный доступ). В этом случае каждая специализация шаблона становится дружественной, независимо от того, является ли она неявно инстанцированной, частично специализированной или явно специализированной.
class A { template<typename T> friend class B; // каждый B<T> является другом A template<typename T> friend void f(T) {} // каждая f<T> является другом A };
Объявления дружественных функций не могут ссылаться на частичные специализации, но могут ссылаться на полные специализации:
template<class T> class A {}; // первичный template<class T> class A<T*> {}; // частичный template<> class A<int> {}; // полный class X { template<class T> friend class A<T*>; // Ошибка friend class A<int>; // OK };
Когда объявление друга ссылается на полную специализацию шаблона функции, ключевые слова inline , constexpr (since C++11) , consteval (since C++20) и аргументы по умолчанию не могут быть использованы:
template<class T> void f(int); template<> void f<int>(int); class X { friend void f<int>(int x = 1); // ошибка: аргументы по умолчанию не разрешены };
Объявление шаблонного друга может указывать на член шаблона класса A, который может быть либо функцией-членом, либо типом-членом (тип должен использовать
уточненный-спецификатор-типа
). Такое объявление является корректным только если последний компонент в его вложенном-спецификаторе-имени (имя слева от последнего
::
) является simple-template-id (имя шаблона, за которым следует список аргументов в угловых скобках), которое именует шаблон класса. Параметры шаблона такого объявления шаблонного друга должны быть выводимы из simple-template-id.
В данном случае член любой специализации A или частичной специализации A становится другом. Это не требует инстанцирования основного шаблона A или частичных специализаций A: единственные требования заключаются в том, чтобы успешно завершилась дедукция параметров шаблона A из этой специализации, и чтобы подстановка выведенных аргументов шаблона в объявление друга давала объявление, которое было бы допустимым повторным объявлением члена специализации:
// основной шаблон template<class T> struct A { struct B {}; void f(); struct D { void g(); }; T h(); template<T U> T i(); }; // полная специализация template<> struct A<int> { struct B {}; int f(); struct D { void g(); }; template<int U> int i(); }; // другая полная специализация template<> struct A<float*> { int *h(); }; // нетемплейтный класс, предоставляющий дружественный доступ членам шаблона класса A class X { template<class T> friend struct A<T>::B; // все A<T>::B являются друзьями, включая A<int>::B template<class T> friend void A<T>::f(); // A<int>::f() не является другом, потому что её сигнатура // не совпадает, но например A<char>::f() является другом // template<class T> // friend void A<T>::D::g(); // некорректно, последняя часть nested-name-specifier, // // D в A<T>::D::, не является simple-template-id template<class T> friend int* A<T*>::h(); // все A<T*>::h являются друзьями: // A<float*>::h(), A<int*>::h(), и т.д. template<class T> template<T U> // все инстанциации A<T>::i() и A<int>::i() являются друзьями, friend T A<T>::i(); // и тем самым все специализации этих шаблонов функций };
|
Аргументы шаблона по умолчанию разрешены в объявлениях дружественных шаблонов только если объявление является определением и никакие другие объявления этого шаблона функции не появляются в данной единице трансляции. |
(начиная с C++11) |
Шаблонные дружественные операторы
Распространённый случай использования дружественных шаблонов — объявление перегруженного оператора, не являющегося членом класса, который работает с шаблоном класса, например operator << ( std:: ostream & , const Foo < T > & ) для некоторого пользовательского Foo < T > .
Такой оператор может быть определен в теле класса, что приводит к генерации отдельного нешаблонного
operator
<<
для каждого
T
и делает этот нешаблонный
operator
<<
другом своего
Foo
<
T
>
:
#include <iostream> template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // generates a non-template operator<< for this T friend std::ostream& operator<<(std::ostream& os, const Foo& obj) { return os << obj.data; } }; int main() { Foo<double> obj(1.23); std::cout << obj << '\n'; }
Вывод:
1.23
или шаблон функции должен быть объявлен как шаблон до тела класса, в этом случае объявление друга внутри
Foo
<
T
>
может ссылаться на полную специализацию
operator
<<
для его
T
:
#include <iostream> template<typename T> class Foo; // предварительное объявление для возможности объявления функции template<typename T> // объявление std::ostream& operator<<(std::ostream&, const Foo<T>&); template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // ссылается на полную специализацию для данного конкретного T friend std::ostream& operator<< <> (std::ostream&, const Foo&); // примечание: это основано на выводе аргументов шаблона в объявлениях // также можно указать аргумент шаблона с помощью operator<< <T> }; // определение template<typename T> std::ostream& operator<<(std::ostream& os, const Foo<T>& obj) { return os << obj.data; } int main() { Foo<double> obj(1.23); std::cout << obj << '\n'; }
Связывание
Спецификаторы класса хранения не допускаются в объявлениях друзей.
|
Если функция или шаблон функции впервые объявлены и определены в объявлении friend, и объемлющий класс определен внутри экспортируемых объявлений , её имя имеет ту же линковку, что и имя объемлющего класса. |
(since C++20) |
Если (до C++20) Иначе, если (начиная с C++20) функция или шаблон функции объявлены в friend-декларации, и соответствующее не-friend объявление достижимо, имя имеет линковку, определённую из этого предыдущего объявления.
В противном случае, связывание имени, введённого объявлением friend, определяется обычным образом.
Примечания
Дружба не транзитивна (друг вашего друга не является вашим другом).
Дружба не наследуется (дети ваших друзей не являются вашими друзьями, а ваши друзья не являются друзьями ваших детей).
Спецификаторы доступа не влияют на смысл объявлений дружественных функций (они могут появляться в private : или в public : секциях, без какой-либо разницы).
Объявление класса-друга не может определять новый класс ( friend class X { } ; является ошибкой).
Когда локальный класс объявляет неквалифицированную функцию или класс как друга, только функции и классы из самой внутренней не-классовой области видимости подвергаются поиску , а не глобальные функции:
class F {}; int f(); int main() { extern int g(); class Local // Локальный класс в функции main() { friend int f(); // Ошибка, нет объявления такой функции в main() friend int g(); // OK, есть объявление для g в main() friend class F; // дружит с локальным F (определенным позже) friend class ::F; // дружит с глобальным F }; class F {}; // локальный F }
Имя, впервые объявленное в объявлении friend внутри класса или шаблона класса
X
, становится членом ближайшего охватывающего пространства имён
X
, но не видимо для поиска (за исключением поиска, зависимого от аргументов, который учитывает
X
), пока не предоставлено соответствующее объявление в области пространства имён - подробности смотрите в разделе
namespaces
.
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_variadic_friend
|
202403L
|
(C++26) | Вариативные объявления друзей |
Ключевые слова
Пример
Операторы вставки и извлечения потока часто объявляются как дружественные функции-нечлены:
#include <iostream> #include <sstream> class MyClass { int i; // friends have access to non-public, non-static static inline int id{6}; // and static (possibly inline) members friend std::ostream& operator<<(std::ostream& out, const MyClass&); friend std::istream& operator>>(std::istream& in, MyClass&); friend void change_id(int); public: MyClass(int i = 0) : i(i) {} }; std::ostream& operator<<(std::ostream& out, const MyClass& mc) { return out << "MyClass::id = " << MyClass::id << "; i = " << mc.i; } std::istream& operator>>(std::istream& in, MyClass& mc) { return in >> mc.i; } void change_id(int id) { MyClass::id = id; } int main() { MyClass mc(7); std::cout << mc << '\n'; // mc.i = 333*2; // error: i is a private member std::istringstream("100") >> mc; std::cout << mc << '\n'; // MyClass::id = 222*3; // error: id is a private member change_id(9); std::cout << mc << '\n'; }
Вывод:
MyClass::id = 6; i = 7 MyClass::id = 6; i = 100 MyClass::id = 9; i = 100
Отчеты о дефектах
Следующие отчеты о дефектах, изменяющих поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 45 | C++98 |
члены класса, вложенного в дружественный
класс для
T
, не имеют специального доступа к
T
|
вложенный класс имеет тот же
доступ, что и объемлющий класс |
| CWG 500 | C++98 |
дружественный класс для
T
не может наследовать от приватных или
защищенных членов
T
, но его вложенный класс может
|
оба могут наследовать
от таких членов |
| CWG 1439 | C++98 |
правило, касающееся дружественных объявлений в нелокальных
классах, не охватывало объявления шаблонов |
охватывает |
| CWG 1477 | C++98 |
имя, впервые объявленное в дружественном объявлении внутри класса
или шаблона класса, не было видимо для поиска, если соответствующее объявление предоставлено в другой области видимости пространства имен |
оно видимо для
поиска в этом случае |
| CWG 1804 | C++98 |
когда член шаблона класса объявляется дружественным, соответствующий
член специализаций частичных специализаций шаблона класса не был другом класса, предоставляющего дружбу |
такие члены
также являются друзьями |
| CWG 2379 | C++11 |
дружественные объявления, ссылающиеся на полные специализации
шаблонов функций, могли быть объявлены constexpr |
запрещено |
| CWG 2588 | C++98 | связности имен, введенных дружественными объявлениями, были неясны | прояснены |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
-
- 11.8.4 Друзья [class.friend]
-
- 13.7.5 Друзья [temp.friend]
- Стандарт C++20 (ISO/IEC 14882:2020):
-
- 11.9.3 Друзья [class.friend]
-
- 13.7.4 Друзья [temp.friend]
- Стандарт C++17 (ISO/IEC 14882:2017):
-
- 14.3 Друзья [class.friend]
-
- 17.5.4 Друзья [temp.friend]
- Стандарт C++14 (ISO/IEC 14882:2014):
-
- 11.3 Друзья [class.friend]
-
- 14.5.4 Друзья [temp.friend]
- Стандарт C++11 (ISO/IEC 14882:2011):
-
- 11.3 Друзья [class.friend]
-
- 14.5.4 Друзья [temp.friend]
- Стандарт C++98 (ISO/IEC 14882:1998):
-
- 11.3 Друзья [class.friend]
-
- 14.5.3 Друзья [temp.friend]
Смотрите также
| Class types | определяет типы, содержащие несколько членов данных |
| Access specifiers | определяет видимость членов класса |