Объявление друга
Объявление друга появляется в теле класса и предоставляет функции или другому классу доступ к закрытым и защищённым членам класса, в котором появляется объявление друга.
Синтаксис
friend function-declaration | (1) | |
friend function-definition | (2) | |
friend elaborated-type-specifier ; | (3) | (до C++26) |
friend simple-type-specifier ;
| (4) |
(начиная с C++11) (до C++26) |
friend friend-type-specifier-list ; | (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; // every B<T> is a friend of A
template<typename T>
friend void f(T) {} // every f<T> is a friend of A
};
Объявления друзей не могут ссылаться на частичные специализации, но могут ссылаться на полные специализации:
template<class T>
class A {}; // primary
template<class T>
class A<T*> {}; // partial
template<>
class A<int> {}; // full
class X
{
template<class T>
friend class A<T*>; // Error
friend class A<int>; // OK
};
Когда объявление друга ссылается на полную специализацию шаблона функции, нельзя использовать ключевое слово inline, constexpr(начиная с C++11), consteval(начиная с C++20) и аргументы по умолчанию:
template<class T>
void f(int);
template<>
void f<int>(int);
class X
{
friend void f<int>(int x = 1); // error: default args not allowed
};
Объявление шаблонного друга может указывать на член шаблона класса A, которым может быть функция-член или тип-член (для типа необходимо использовать elaborated-type-specifier). Такое объявление корректно сформировано только в том случае, если последний компонент его вложенного спецификатора имени (имя слева от последнего ::) является simple-template-id (именем шаблона, за которым в угловых скобках следует список аргументов), задающим шаблон класса. Параметры шаблона такого объявления шаблонного друга должны выводиться из simple-template-id.
В этом случае член любого экземпляра A или частичной специализации A становится другом. При этом первичный шаблон A или частичные специализации A не инстанцируются: требуется лишь, чтобы вывод параметров шаблона A из этой специализации завершился успешно, а подстановка выведенных аргументов шаблона в объявление друга дала объявление, которое было бы корректным повторным объявлением члена специализации:
// primary template
template<class T>
struct A
{
struct B {};
void f();
struct D { void g(); };
T h();
template<T U>
T i();
};
// full specialization
template<>
struct A<int>
{
struct B {};
int f();
struct D { void g(); };
template<int U>
int i();
};
// another full specialization
template<>
struct A<float*>
{
int *h();
};
// the non-template class granting friendship to members of class template A
class X
{
template<class T>
friend struct A<T>::B; // all A<T>::B are friends, including A<int>::B
template<class T>
friend void A<T>::f(); // A<int>::f() is not a friend because its signature
// does not match, but e.g. A<char>::f() is a friend
// template<class T>
// friend void A<T>::D::g(); // ill-formed, the last part of the nested-name-specifier,
// // D in A<T>::D::, is not simple-template-id
template<class T>
friend int* A<T*>::h(); // all A<T*>::h are friends:
// A<float*>::h(), A<int*>::h(), etc
template<class T>
template<T U> // all instantiations of A<T>::i() and A<int>::i() are friends,
friend T A<T>::i(); // and thereby all specializations of those function templates
};
| Аргументы шаблона по умолчанию разрешены в объявлениях шаблонных друзей только в том случае, если объявление является определением и в этой единице трансляции нет других объявлений этого шаблона функции. | (начиная с 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; // forward declare to make function declaration possible
template<typename T> // declaration
std::ostream& operator<<(std::ostream&, const Foo<T>&);
template<typename T>
class Foo
{
public:
Foo(const T& val) : data(val) {}
private:
T data;
// refers to a full specialization for this particular T
friend std::ostream& operator<< <> (std::ostream&, const Foo&);
// note: this relies on template argument deduction in declarations
// can also specify the template argument with operator<< <T>"
};
// definition
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';
}
Связывание
Спецификаторы класса хранения не допускаются в объявлениях друзей.
| Если функция или шаблон функции впервые объявляется и определяется в объявлении друга, а охватывающий класс определён внутри экспортирующих объявлений, его имя имеет такое же связывание, как имя охватывающего класса. | (начиная с C++20) |
Если(до C++20)В противном случае, если(начиная с C++20) функция или шаблон функции объявлены в объявлении друга и доступно соответствующее объявление не-друга, имя имеет связывание, определяемое этим предыдущим объявлением.
В противном случае связывание имени, введённого объявлением друга, определяется обычным образом.
Примечания
Дружба не является транзитивной (друг вашего друга не становится вашим другом).
Дружба не наследуется (дети вашего друга не становятся вашими друзьями, а ваши друзья не становятся друзьями ваших детей).
Спецификаторы доступа не влияют на смысл объявлений друзей (они могут находиться в секциях private: или public:, и разницы не будет).
Объявление класса-друга не может определять новый класс (friend class X {}; является ошибкой).
Когда локальный класс объявляет неквалифицированную функцию или класс другом, поиск выполняется только среди функций и классов во вложенной области видимости, ближайшей к внешней, которая не является областью видимости класса, а не среди глобальных функций:
class F {};
int f();
int main()
{
extern int g();
class Local // Local class in the main() function
{
friend int f(); // Error, no such function declared in main()
friend int g(); // OK, there is a declaration for g in main()
friend class F; // friends a local F (defined later)
friend class ::F; // friends the global F
};
class F {}; // local F
}
Имя, впервые объявленное в объявлении друга внутри класса или шаблона класса, X становится членом пространства имён, непосредственно охватывающего X, но не видимо при поиске (за исключением поиска с учётом аргументов, который рассматривает X), если только не предоставлено соответствующее объявление на уровне пространства имён — подробности см. в разделе пространства имён.
| Макрос проверки поддержки возможности | Значение | Стандарт | Возможность |
|---|---|---|---|
__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]
См. также
| Типы классов | определяют типы, содержащие несколько элементов данных |
| Спецификаторы доступа | определяют видимость членов класса |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/friend