C++中的friend class与friend function是什么?(如何合理控制私有权限)

3次阅读

friend class 在特定协作场景下合理使用可避免暴露接口,如 iterator 访问 container 私有数组;需前向声明、不传递、不继承、增加头文件依赖;friend function 适合对称操作如 operator

C++中的friend class与friend function是什么?(如何合理控制私有权限)

friend class 会打破封装,但只在特定协作场景下值得用

它让另一个类能直接访问当前类的所有私有成员,不是为了偷懒绕过访问控制,而是解决两个紧密耦合类之间“不得不共享内部状态”的问题。比如 Iterator 类需要遍历 Container 的底层数组,而这个数组必须是私有的——这时把 Iterator 声明为 Containerfriend class,比暴露 get_raw_data() 或把数组设为 public 更干净。

常见错误现象:friend class B; 写在 A 类定义里,但编译报错说 B 未定义——这是因为 B 类型名在声明时还不可见,得提前声明 class B;(前向声明)。

  • 只能在类定义内部声明,不能在类外补加
  • 不具有传递性:A 是 B 的 friend,B 是 C 的 friend,不等于 A 能访问 C 的私有成员
  • 不会继承:派生类不会自动获得基类 friend 的权限
  • 头文件依赖变重:friend class X; 意味着你得 #include X 的定义,或至少前向声明

friend function 适合做对称操作,比如 operator

当某个函数逻辑上不属于任何一方、又需要访问双方私有成员时,friend 函数是唯一选择。最典型的是流输出运算符:operator 左操作数是 <code>std::ostream&,不可能把它塞进你的类里;但它又得读你的私有字段,所以必须声明为 friend

使用场景举例:实现两个自定义类的相等比较 operator==(const A&, const B&),且双方都有不可公开的内部标识字段。

立即学习C++免费学习笔记(深入)”;

  • 声明时写全签名:friend std::ostream& operator
  • 定义可以放在类外(通常在 .cpp 里),不用加 friend 关键字
  • 如果函数模板要成为 friend,需显式指定模板参数或使用友元模板声明,否则容易因 ADL 失效而调用不到
  • 避免把普通工具函数(如 validate())设为 friend——它本该通过 public 接口完成工作

别用 friend 绕过设计缺陷,否则私有字段形同虚设

看到 “我需要访问 private 成员” 就加 friend,大概率说明类职责没划清。比如一个 Database 类把连接句柄藏成私有,结果所有业务类都申请成为它的 friend 去执行 SQL——这其实是 Database 缺少足够粒度的 public 方法(如 execute_query()),而不是 friend 用得不够多。

容易踩的坑:

  • 测试类(如 MyClassTest)不该是 friend:应测 public 行为,而非钻进去检查私有字段值
  • 把整个第三方库的命名空间设为 friend(friend namespace boost;)——c++ 不支持,而且极其危险
  • 误以为 friend 提升性能:它只是取消访问检查,不影响内联、拷贝或内存布局
  • 在模板类中声明 friend 时,若未用 template<typename t></typename> 显式关联,会导致特化版本无法访问

替代方案往往更可持续:组合 + 精细接口 > 暴露内部

多数时候,与其拉一个 friend 进来,不如在类里加一个受控的访问入口。比如把 std::vector<int> data_;</int> 私有成员,换成 const std::vector<int>& raw_data() const { return data_; }</int> ——既不破坏封装,又满足只读需求。

性能影响很小:返回 const 引用几乎零开销;如果担心别名问题,可用 std::span<const int></const> 替代(C++20)。

  • 优先提供 const 访问器,而非非 const 的 getter
  • std::optional<t></t> 或状态码表达“可能不存在”的访问结果,比返回裸指针安全
  • 对调试用途的私有状态,考虑用宏控制是否编译进 release 版本,而不是靠 friend 开后门

真正难处理的是跨模块的深度协作——比如解析器和语法树节点。这时候 friend 不是捷径,而是明确的契约:你得同步维护双方的内部变更,稍有不慎就崩。这种耦合,写注释比写代码还重要。

text=ZqhQzanResources