C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
友元声明
友元声明出现于类体内,并向一个函数或另一个类授予对包含友元声明的类的私有及受保护成员的访问权。
语法
friend 函数声明 | (1) | ||||||||
friend 函数定义 | (2) | ||||||||
friend 详述类说明符 ;
|
(3) | ||||||||
friend 简单类型说明符 ;
friend typename-说明符 |
(4) | (C++11 起) | |||||||
描述
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; // 此为非成员函数 } public: void member_set(int i) { a = i; // 此为成员函数 } };
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]
参阅
类声明 | |
访问说明符 |