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; // 私有成员 // 非成员函数 operator<< 将有权访问 Y 的私有成员 friend std::ostream& operator<<(std::ostream& out, const Y& o); friend char* X::foo(int); // 其他类的成员也可以成为友元 friend X::X(char), X::~X(); // 构造函数和析构函数也可以成为友元 }; // 友元声明并不声明成员函数 // 这个 operator<< 仍需要作为非成员函数进行定义 std::ostream& operator<<(std::ostream& out, const Y& y) { return out << y.data; // 可以访问私有成员 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; // 私有数据成员 class B {}; // 私有嵌套类型 enum { a = 100 }; // 私有枚举项 friend class X; // 友元类前向声明(详细类型说明符) friend Y; // 友元类声明(简单类型说明符)(C++11起) // 自C++26起上述两个友元声明可合并: // friend class X, Y; }; class X : A::B // 正确:A::B对友元可访问 { A::B mx; // 正确:A::B对友元成员可访问 class Y { A::B my; // 正确:A::B对友元的嵌套成员可访问 }; int v[A::a]; // 正确:A::a对友元成员可访问 };
模板友元
无论是
函数模板
还是
类模板
声明,都可以在任意非局部类或类模板中使用
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>; // 正确 };
当友元声明引用函数模板的完全特化时,不能使用关键字 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); // 错误:不允许使用默认参数 };
模板友元声明可以指名类模板A的成员,该成员可以是成员函数或成员类型(该类型必须使用
详述类型说明符
)。此类声明仅当其嵌套名称说明符中的最后一个组成部分(最后一个
::
左侧的名称)是命名该类模板的简单模板ID(模板名称后跟尖括号中的实参列表)时,才是良构的。此类模板友元声明的模板参数必须可从简单模板ID中推导得出。
在此情况下,任何特化版本或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(); // 非良构,嵌套名称说明符的最后部分 // // A<T>::D::中的D不是简单模板标识 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 起) |
模板友元运算符
模板友元的一个常见用例是声明作用于类模板的非成员运算符重载,例如针对用户自定义的 Foo < T > 实现 operator << ( std:: ostream & , const Foo < T > & ) 。
此类运算符可在类体内定义,其效果是为每个
T
生成独立的非模板
operator
<<
,并使该非模板
operator
<<
成为其
Foo
<
T
>
的友元:
#include <iostream> template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // 为该类型 T 生成非模板 operator<< 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
>
内部的友元声明可以引用针对其
T
的
operator
<<
完全特化:
#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'; }
链接
存储类说明符 不允许在友元声明中使用。
|
若函数或函数模板首次在友元声明中被声明并定义,且其外围类在 导出声明 中被定义,则该函数名称具有与外围类名称相同的链接属性。 |
(since C++20) |
若 (C++20 前) 否则,若 (C++20 起) 函数或函数模板在友元声明中声明,且存在可达的 对应非友元声明 ,则该名称的链接性由先前的声明决定。
否则,由友元声明引入的名称的链接性将按常规方式确定。
注释
友谊不具备传递性(你朋友的朋友并非你的朋友)。
友元关系不可继承(你朋友的孩子不是你的朋友,你的朋友也不是你孩子的朋友)。
访问说明符 对友元声明的含义没有影响(它们可以出现在 private : 或 public : 区域中,效果没有区别)。
友元类声明不能定义新类( friend class X { } ; 是错误的)。
当局部类声明一个非限定函数或类为友元时,仅在 最内层非类作用域 中 查找 函数和类,而不会查找全局函数:
class F {}; int f(); int main() { extern int g(); class Local // main() 函数中的局部类 { friend int f(); // 错误:main() 中未声明此函数 friend int g(); // 正确:main() 中存在 g 的声明 friend class F; // 友元为局部 F(稍后定义) friend class ::F; // 友元为全局 F }; class F {}; // 局部 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++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| 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 | 定义类成员的可见性 |