跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

c++的friend声明有哪些合理的用例? (单元测试与序列化)

friend 是C++中可控破例机制,用于单元测试和序列化时安全访问私有成员;不应滥用作跨模块接口,且需注意ODR、ADL及PIMPL等场景的编译与ABI风险。 friend 用于单元测试时绕过私有访问限制 当类的私有成员(如内部状态、缓存字段、未公开的辅助函数)需要被测试验证,但又不应暴露为 public 或 protected 时,
friend
是最轻量的可控破例方式。它比把测试类塞进继承体系更干净,也比把所有待测成员设为
public
更安全。 常见错误是直接将整个测试类声明为 friend,导致耦合过重;更合理的做法是只 friend 具体的测试函数(C++11 起支持),或用匿名命名空间内的自由函数。 测试函数必须在类定义前已声明,否则编译器不识别 若测试代码在 .cpp 文件中,需确保该函数声明在头文件中可见(通常放在类定义下方、同一头文件内) Clang 和 GCC 对 friend 函数的 ODR(One Definition Rule)检查较严格,避免在多个 TU 中重复定义 friend 函数体
class BankAccount { private: double balance_ = 0.0; int transaction_count_ = 0; void log_transaction() { ++transaction_count_; } // 只允许特定测试函数访问 friend void test_balance_and_count(); };
friend 用于序列化/反序列化逻辑解耦 当使用非侵入式序列化库(如
boost::serialization
、
cereal
)时,
friend
是标准推荐方案:它让序列化函数能直接读写私有成员,同时不破坏封装边界——序列化逻辑本身不在类内部,也不依赖 public 接口模拟数据结构。 关键点在于:序列化函数不是类的接口,而是外部协议适配层。把它设为 friend,语义上清晰表达了“此函数获准以实现为目的访问内部表示”,而非“此函数是类的一部分”。 立即学习 “ C++免费学习笔记(深入) ”; C函数速查手册(CHM版) C函数速查手册(CHM版) 下载
boost::serialization
要求
serialize
成员函数为
template
并声明为
friend
,否则无法访问私有字段 使用
cereal
时,
CEREAL_CLASS_VERSION
不需要 friend,但自定义
save
/
load
函数若访问私有成员,则必须 friend 注意 ADL(Argument-Dependent Lookup):某些序列化库依赖友元函数参与查找,若忘记 friend,可能静默调用默认泛型版本,导致序列化内容为空或崩溃
class Config { private: std::string api_key_; int timeout_ms_; friend class cereal::access; template void serialize(Archive& ar) { ar(CEREAL_NVP(api_key_), CEREAL_NVP(timeout_ms_)); } };
friend 不该用于跨模块接口或替代设计重构 有人用
friend
让另一个业务类(比如
Logger
或
Validator
)直接读写本类私有成员,这本质上是把封装契约交给了编译期信任,而非运行时契约。一旦 friend 类变更内部逻辑,原类就可能静默失效。 真正合理的替代路径通常是:提取观察者接口(如
get_debug_state()
)、引入回调机制、或用 visitor 模式。只有当性能压倒一切(如高频日志中避免 getter 拷贝)且 friend 类与本类生命周期/演进强绑定时,才考虑此用法。 friend 声明不能出现在类模板特化之外的局部 作用域 ,也不能在函数体内 friend 关系不可继承:基类的 friend 不能访问派生类新增的私有成员 多个 friend 声明不会自动聚合权限;每个都必须显式写出 容易被忽略的链接与 ABI 风险 friend 函数的符号名会进入类所在命名空间的关联集(associated namespaces),影响 ADL 行为。更隐蔽的问题是:如果 friend 函数定义在头文件中且含内联实现,而它又调用了类的私有构造/析构,那么不同编译单元对私有成员的访问布局理解必须一致——这在开启不同优化等级或混用 STL 实现时可能出问题。 尤其要注意 PIMPL 惯用法下 friend 的使用:若 friend 函数直接操作
pimpl_
指针所指对象的私有成员,而该对象定义在 .cpp 中,头文件里只有前向声明,此时 friend 声明本身合法,但函数定义处若尝试访问其私有成员,就会编译失败。

相关文章