友元声明出现于类体内,并向一个函数或另一个类授予对包含友元声明的类的私有及受保护成员的访问权。
语法
friend 函数声明 | (1) | |
friend 函数定义 | (2) | |
friend 详述类说明符 ; | (3) | |
friend 简单类型说明符 ; friend typename-说明符 ; | (4) | (C++11 起) |
描述
1) 指明一个或数个函数为此类的友元
- 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
- }
2) (只允许在非局部类的定义中使用)定义一个非成员函数,同时令其为此类的友元。这种非成员函数始终为内联函数。
- class X {
- int a;
- friend void friend_set(X& p, int i) {
- p.a = i; // 此为非成员函数
- }
- public:
- void member_set(int i) {
- a = i; // 此为成员函数
- }
- };
3) 指定以 详述类说明符(见详述类型说明符)指名的 class、struct 或 union 为此类的友元。这意味着,该友元的各成员声明和定义可以访问此类的私有与受保护成员,而且该友元也能从此类的私有或受保护成员进行继承。不需要提前声明用于此友元声明的类名。
4) 指定以 简单类型说明符 或 typename-说明符 指名的类型,若该类型是(可有 cv 限定的)class、struct 或 union,则为此类的友元;否则忽略该 friend
声明。此声明不前置声明新类型。
- class Y {};
- class A {
- int data; // 私有数据成员
- class B { }; // 私有嵌套类型
- enum { a = 100 }; // 私有枚举项
- friend class X; // 友元类的前置声明(详述类型说明符)
- friend Y; // 友元类声明(简单类型说明符) (C++11 起)
- };
- class X : A::B // OK:友元能访问 A::B
- {
- A::B mx; // OK:友元的成员能访问 A::B
- class Y {
- A::B my; // OK:友元的嵌套成员能访问 A::B
- };
- int v[A::a]; // OK:友元的成员能访问 A::a
- };
注解
友元关系不传递(你朋友的朋友不是你的朋友)。
友元关系不继承(你朋友的孩子不是你的朋友)。
友元函数声明中不允许使用存储类说明符。在友元声明中定义的函数具有外部连接,先前已定义的函数保持其定义时所具有的连接。
访问说明符对于友元声明的含义没有影响(它们可出现于 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,main() 中有 g 的声明
- friend class F; // 令局部 F(随后定义)为友元
- friend class ::F; // 令全局 F 为友元
- };
- class F {}; // 局部 F
- }
在类或类模板 X 中的友元声明中被首次声明的名字,成为 X 的最内层外围命名空间的成员,但对于查找不可见(除了考虑 X 的实参依赖查找),除非在该命名空间作用域中提供与之匹配的声明——细节见命名空间。
模板友元
函数模板与类模板声明,都可带 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
和默认实参。
- template<class T> void f(int);
- template<> void f<int>(int);
- class X {
- friend void f<int>(int x = 1); // 错误:不允许默认实参
- };
模板友元声明可以指名类模板 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>
内的友元声明可以涉指 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';
- }
示例
流插入与提取运算符往往声明为非成员友元
运行此代码
- #include <iostream>
- #include <sstream>
- class MyClass {
- int i;
- friend std::ostream& operator<<(std::ostream& out, const MyClass& o);
- friend std::istream& operator>>(std::istream& in, MyClass& o);
- public:
- MyClass(int i = 0) : i(i) {}
- };
- std::ostream& operator<<(std::ostream& out, const MyClass& mc)
- {
- return out << mc.i;
- }
- std::istream& operator>>(std::istream& in, MyClass& mc)
- {
- return in >> mc.i;
- }
- int main()
- {
- MyClass mc(7);
- std::cout << mc << '\n';
- std::istringstream("100") >> mc;
- std::cout << mc << '\n';
- }
输出:
- 7
- 100
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 45 | C++98 | T 的友元类的嵌套类的成员对 T 没有特殊访问权 | 嵌套类拥有和外围类相同的访问权 |
CWG 500 | C++98 | T 的友元类不能继承自 T 的私有与受保护成员,但其嵌套类可以 | 都可以继承自此种成员 |
引用
- C++11 standard (ISO/IEC 14882:2011):
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- C++98 standard (ISO/IEC 14882:1998):
- 11.3 Friends [class.friend]
- 14.5.3 Friends [temp.friend]