友元函数能访问私有成员但不破坏封装性,它只是被授权的外部函数,无this指针,不属类成员;必须在类内声明、类外定义;典型用途是运算符重载和跨类深度协作。

友元函数能访问私有成员,但不改变类的封装边界
友元函数不是类的成员,也不受访问控制符限制,但它本身不“属于”该类——它只是被授权读写类的私有/保护成员。这和把私有成员改成 public 有本质区别:friend 不扩大接口暴露面,只在特定函数粒度上开后门。
常见误用是以为加了 friend 就等于“这个函数成了类的一部分”,其实它连 this 指针都没有,也不能直接调用非静态成员函数(除非显式传入对象)。
- 友元声明必须出现在类定义内部,位置不限(
public/private/protected区域均可) - 声明时只需写函数签名,无需实现;实现放在类外,和普通函数一样
- 若友元函数依赖模板参数(如
template),需在类内完整声明模板friend void foo(T&);
什么时候必须用 friend 而不是公有 getter/setter
典型场景是运算符重载,尤其是左操作数不是当前类对象的情况。比如 operator 输出流重载:左操作数是 std::ostream&,你无法把它塞进类成员函数里(因为 this 会是 ostream 类型)。
class Vec {
int x_, y_;
public:
Vec(int x, int y) : x_(x), y_(y) {}
friend std::ostream& operator<<(std::ostream& os, const Vec& v) {
return os << "(" << v.x_ << ", " << v.y_ << ")"; // 直接访问 x_, y_
}
};
另一个不可替代的场景是某些跨类协作逻辑,例如两个类需要深度互访对方私有状态(如容器与迭代器),且设计上不允许暴露中间接口。
立即学习“C++免费学习笔记(深入)”;
- 避免为单个字段写一堆
get_x()/set_x()再让外部函数组合使用——效率低、语义碎 - 如果 getter/setter 已经足够表达需求,就别用
friend;它不该是“图方便”的快捷键 - 注意:友元关系不可继承,子类不会自动获得对基类友元函数的访问权
friend 声明的常见编译错误和陷阱
最典型的错误是声明和定义不匹配,导致链接失败或“undefined reference”。C++ 不要求友元函数在类声明前已可见,但必须确保定义存在且签名一致。
例如下面这段代码会报错:
class A {
int val_;
friend void foo(A&); // 声明
};
void foo(const A& a) { ... } // 错!参数是 const A&,和声明不一致
- 友元函数的声明不参与 ADL(参数依赖查找),所以如果只在类内声明而没在命名空间中定义,可能找不到函数
- 多个类互相
friend 时,注意头文件包含顺序;建议把友元声明放在类定义末尾,定义统一放在 .cpp 文件 - 不能在局部类或匿名命名空间中声明友元(C++11 起允许局部类有友元,但限制极多,慎用)
friend 真的破坏封装性吗?关键看谁控制访问权
封装性的核心不是“不让别人看到”,而是“由谁决定谁能看、怎么看”。friend 把访问权限的决策权保留在类作者手里——只有类定义者能添加友元,调用方无法自行突破限制。
相比之下,把成员设为 public 是彻底放弃控制;而过度使用 getter/setter 则把校验逻辑外移,反而削弱了类内不变量的保障能力。
- 一个类可以有多个友元函数,也可以有友元类(整个类的所有成员函数都获得访问权)
- 友元函数仍受命名空间和链接属性约束;它不能绕过
static或inline的作用域规则 - 现代 C++ 中,比起裸
friend,更推荐结合make_friend模式或 Pimpl 手法控制暴露粒度
真正容易被忽略的是:友元带来的耦合是双向的。一旦某个函数被声明为友元,它和类的私有布局就绑定了——改成员名、类型或顺序,可能悄无声息地破坏友元函数行为,且编译器不一定报错。










