友元类是单向授权,A声明B为friend后B可访问A的私有成员,但A不能访问B的;声明必须在A类定义体内,用friend class B;形式,前置声明class B;即可;友元关系不可继承,且不授予对private构造/析构函数的调用权。

friend 类声明必须写在类内部
友元类不是“互相友好”,而是单向授权:A 声明 B 为 friend,B 就能访问 A 的私有成员,但 A 不能因此访问 B 的私有成员。声明位置很关键——friend 关键字必须出现在被访问类(比如 ClassA)的定义体内,且通常放在 private 或 protected 区域里,但它本身不改变访问级别。
常见错误是把 friend class B; 写在类外面,或者写成 friend B;(漏掉 class 关键字,C++ 要求显式写出类型类别)。
-
friend class B;是合法声明;friend B;编译报错:'B' does not name a type - 前置声明足够:如果
B在A定义前未完全定义,只需在A上方加class B;即可 - 友元关系不可继承:即使
B是A的 friend,B的派生类也不能访问A的私有成员
友元类能直接访问 private 成员,无需 public 接口
一旦声明成立,友元类的**任何成员函数**(包括构造函数、静态函数、const 成员函数)都能像访问自己成员一样读写目标类的 private 和 protected 成员,不需要 getter/setter,也不受封装限制。
示例中,class Printer 是 class Data 的 friend,那么 Printer::print() 可以直接写 d.m_value(假设 m_value 是 Data 的 private 成员),而普通函数或非友元类只能通过 Data 提供的 public 方法间接访问。
立即学习“C++免费学习笔记(深入)”;
- 友元函数同理,但只限于那个函数;友元类则授权整个类的所有成员函数
- 注意:友元不授予对
private构造函数/析构函数的调用权——除非你显式在友元类中调用,且该构造函数可访问(例如不是 delete 或 private 继承导致不可见) - 若
Data的私有成员是另一个类的 private 成员(如嵌套类),友元关系不自动穿透;Printer仍不能直接访问Data::Inner::m_x,除非Inner也声明Printer为 friend
友元破坏封装,但适合紧密耦合场景
典型适用场景是两个类逻辑上高度内聚,比如容器与迭代器(std::vector 和它的 iterator)、Pimpl 惯用法中的接口类与实现类、或序列化辅助类。这时候强行加 public 接口反而暴露不该暴露的细节,或引入不必要的性能开销(比如拷贝返回值)。
- 不要为图省事给业务逻辑类随便加 friend——它会让类的契约变得模糊,增加维护成本
- 友元声明不会影响二进制兼容性,但会扩大 ABI 影响范围:修改
Data的私有成员布局,可能迫使所有 friend 类重新编译 - 模板类做友元时需谨慎:友元可以是具体特化(
friend class Helper),也可以是全特化模板(; template),后者权限更大,也更难追踪friend class Helper;
编译器不检查友元访问是否合理
C++ 编译器只验证语法和可见性,不管语义是否“合理”。比如 Printer 是 Data 的 friend,它就能在 print() 里把 d.m_value = 42;,哪怕这违反了 Data 的不变量(invariant)。这种越权修改容易埋下 bug,调试时也难以定位——因为错误发生在友元类里,而问题根源其实在被访问类的约束被绕过。
- 没有运行时检查,也没有 warning 提示“你在滥用 friend”
- 单元测试要覆盖友元类对私有状态的修改路径,否则很容易漏掉非法状态
- Clang-Tidy 有
cppcoreguidelines-non-private-member-variables-in-classes等规则,但对 friend 访问无能为力;静态分析工具很难补这个缺口
真正麻烦的不是怎么写 friend,而是后续谁来保证 Printer 不会悄悄改坏 Data 的内部一致性——这得靠设计约定、代码审查和测试覆盖,编译器帮不上忙。










