下面列出几个 高级友元应用场景 与典型设计模式,并配以示例,帮助大家在实际项目中灵活运用 friend
机制。
场景:为某个类型定义非成员操作符(如算术、流插入等),希望通过 Argument-Dependent Lookup(ADL)让调用者只需 #include
类型的头文件即可生效。
做法:在类内部直接定义 friend
函数,而不在命名空间外重复声明。
namespace math {
// 将 operator* 注入到 math 命名空间,ADL 可自动发现
class Vec {
double x,y;
public:
Vec(double _x, double _y): x(_x), y(_y) {}
// 注入式友元
friend Vec operator*(double s, const Vec& v) {
return Vec(s * v.x, s * v.y);
}
friend Vec operator*(const Vec& v, double s) {
return Vec(s * v.x, s * v.y);
}
friend std::ostream& operator<<(std::ostream& os, const Vec& v) {
return os << '(' << v.x << ',' << v.y << ')';
}
};
} // namespace math
// 使用方只需 #include "vec.hpp"
int main() {
using namespace math;
Vec v(1,2);
std::cout << 3 * v + v * 4 << "\n"; // ADL 自动找到 operator*
}
场景:使用 Pimpl(Pointer to Implementation)模式,将实现细节完全隐藏于 .cpp
,降低编译依赖。
做法:让 Impl
类成为接口类的友元,直接操作私有指针与成员。
// widget.hpp
class Widget {
public:
Widget();
~Widget();
void draw();
private:
struct Impl;
Impl* pImpl;
friend struct Impl; // Impl 可直接访问 pImpl
};
// widget.cpp
struct Widget::Impl {
int w,h;
void drawImpl() { /* 绘制逻辑 */ }
};
Widget::Widget(): pImpl(new Impl{640,480}) {}
Widget::~Widget(){ delete pImpl; }
void Widget::draw() { pImpl->drawImpl(); }
场景:在基类中提供默认行为,同时允许派生类定制实现(类似“模板回调”)。
做法:通过 friend
授权基类模板访问派生类私有成员。
template<typename Derived>
class Serializer {
public:
std::string serialize() const {
// 访问 Derived::toString()
return static_cast<const Derived*>(this)->toString();
}
};
class Person : public Serializer<Person> {
std::string name;
int age;
// 授权 Serializer 访问私有成员
friend class Serializer<Person>;
std::string toString() const {
return name + ":" + std::to_string(age);
}
public:
Person(std::string n,int a): name(std::move(n)), age(a) {}
};
场景:在不破坏封装的前提下,让测试框架访问类私有、受保护成员。
做法:在被测类内声明测试类/测试函数为友元。
class MyClass {
private:
int secret();
friend class MyClassTest; // GoogleTest 测试套件
};
int MyClass::secret() { return 42; }
// MyClass_test.cpp
#include "MyClass.hpp"
#include
class MyClassTest : public ::testing::Test { /* … */ };
TEST_F(MyClassTest, Secret) {
MyClass obj;
EXPECT_EQ(obj.secret(), 42); // 直接访问私有函数
}
场景:在数值计算库(如 Eigen、Blaze)中,构建 AST 节点并延迟计算,避免中间拷贝。
做法:各运算节点之间用 friend
提供访问内部节点接口。
template<typename L, typename R>
struct AddExpr {
const L& l; const R& r;
AddExpr(const L& a,const R& b):l(a),r(b){}
double eval(size_t i) const { return l.eval(i) + r.eval(i); }
};
class Vec {
std::vector<double> data;
public:
double eval(size_t i) const { return data[i]; }
template<typename R>
friend AddExpr<Vec, R> operator+(const Vec& a, const R& b) {
return {a,b};
}
// … 其他运算
};
场景:根据类型特征启用/禁用不同版本的非成员函数。
做法:在类内部声明模板友元,并结合 std::enable_if
。
#include
class Container {
// 只有当 T 为可迭代类型时,才注入该友元
template<typename T,
typename = std::enable_if_t<
std::is_same<decltype(std::declval<T>().begin()), typename T::iterator>::value
>>
friend void process(const T& c) {
for (auto& x : c) { /* … */ }
}
};
场景:在模块(module; export module M;
)内部,想让某些实现隐藏于模块界面之外,却又可被同模块其它单元访问。
做法:使用 friend
将模块中的“私有接口”类/函数授权给模块外可见的类型。
// M.ixx (模块接口)
export module M;
export class PublicAPI {
void foo();
};
// M.cppm (模块实现)
module M;
class Helper { /* … */ };
friend class Helper; // 仅在同模块实现单元可访问
void PublicAPI::foo() { /* Helper 可访问 PublicAPI 私有 */ }
在复杂系统中,不同子系统之间有时需要部分越权访问,此时可定义“访问轮廓”接口(Access
)类,将细粒度权限集中管理:
// access.hpp
class SubsysA;
class SubsysB;
class Access {
private:
friend class SubsysA;
friend class SubsysB;
static void grant(A& a, B& b) { /* … */ }
};
// subsys_a.hpp
#include "access.hpp"
class SubsysA {
void doA(A& a, B& b) {
Access::grant(a,b);
}
};
// subsys_b.hpp 同理
通过以上高级用例,可以在 域内注入操作符、隐藏实现细节、构建高性能表达式模板、精细化测试访问、模块内私有互操作 等多种场景下,灵活运用 friend
关键字,既保留封装带来的优势,又实现必要的跨域访问。