友元函数在类内声明、类外定义,非类成员但可访问私有/保护成员;友元类则授予整个类所有成员函数访问权,权限不可继承且易扩大信任范围。

友元函数声明写在类内部,但定义在类外部
友元不是类的成员,它只是被授权访问私有/保护成员的普通函数。常见错误是把 friend 声明和函数定义混在一起,比如在类里直接写函数体——这会让编译器以为是内联定义,但实际它不能访问 this,逻辑上也不符合友元的设计意图。
正确做法是:在类内用 friend 声明函数签名;在类外按普通函数方式定义。
- 类内只写
friend void print(const MyClass& obj);,不带实现 - 类外定义时,不需要加
MyClass::作用域,因为它不是成员函数 - 如果函数模板化(比如
template<typename t></typename>),friend声明必须显式指定模板参数或使用friend模板声明,否则可能绑定到错误特化
友元类的所有成员函数都能访问被授权类的私有成员
声明 friend class Helper; 后,Helper 的每个函数(包括构造、析构、静态/非静态成员)都自动获得访问权。这不是“继承”来的权限,也不受派生关系影响——哪怕 Helper 是空类,只要声明了,就能碰私有字段。
容易踩的坑是误以为只有某个特定函数需要权限,结果把整个类设为友元,扩大了信任范围。尤其当友元类后续不断添加新函数时,权限会无声膨胀。
立即学习“C++免费学习笔记(深入)”;
- 优先考虑友元函数而非友元类,粒度更可控
- 友元类无法被继承:子类不会自动成为原类的友元
- 如果类 A 声明 B 为友元,B 中仍需包含 A 的头文件才能使用其私有类型(如私有嵌套类)
友元破坏封装性,但某些场景下不可替代
典型刚需场景是重载操作符,尤其是左操作数不是当前类对象的情况。比如 std::ostream& operator<<(std::ostream&, const MyClass&),必须是全局函数,又必须读取 MyClass 私有数据——这时友元是标准解法。
另一个常见用途是容器类与其迭代器的配合:迭代器常需直接访问容器的底层指针或节点结构,而这些通常设为私有。强行暴露 getter 不仅效率低,还可能破坏不变量。
- 不要为“方便调试”或“简化测试”滥用友元;测试应走 public 接口,或用
TEST_FRIEND宏(如 gtest 提供)替代 - 友元不参与查找:
foo(obj)调用是否成功,取决于 ADL 或显式作用域,跟友元声明无关 - 多个友元函数共享同一访问权限,但彼此之间无特殊关系——它们只是恰好都被授权了
友元声明不增加类的大小,也不影响二进制兼容性
友元纯粹是编译期权限检查机制,生成的目标代码里没有任何额外字段或虚表项。即使你给一个类加了 20 个友元声明,sizeof(MyClass) 也不会变。
但要注意链接期行为:友元函数定义若放在头文件中且未声明为 inline,可能引发 ODR 违规(多定义错误)。尤其在模板友元和隐式实例化混合时,容易出现符号重复定义。
- 非模板友元函数定义建议放在 .cpp 文件中
- 模板友元若定义在头文件,务必加
inline(C++17 起可省略,但显式写出更清晰) - 友元不影响类的 ABI:修改友元列表不构成二进制不兼容变更
真正麻烦的是跨模块协作时的头文件依赖和权限蔓延——一个类被太多地方设为友元,意味着它的私有实现细节已经散落在各处,改起来就不是改一个类的事了。









