友元关系利大于弊的实际编程场景
- 运算符重载:
- 场景:当需要对自定义类型进行运算符重载时,有时候运算符的操作数中有一个是基本类型,另一个是自定义类类型。例如重载
<<
运算符用于输出自定义类对象。若将 <<
重载为类的成员函数,其左操作数必须是类对象,不符合常规使用习惯。
- 示例:
#include <iostream>
class Point {
int x, y;
public:
Point(int a, int b) : x(a), y(b) {}
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
int main() {
Point p(1, 2);
std::cout << p << std::endl;
return 0;
}
- 访问私有数据进行辅助操作:
- 场景:某些工具类或辅助类可能需要频繁访问目标类的私有成员来完成特定功能,这些功能又不适合作为目标类的公共接口暴露。例如一个用于调试的类,它需要获取并打印目标类的私有状态信息。
- 示例:
class MyClass {
int privateData;
public:
MyClass(int data) : privateData(data) {}
friend class Debugger;
};
class Debugger {
public:
void printPrivateData(const MyClass& obj) {
std::cout << "Private data: " << obj.privateData << std::endl;
}
};
- 类之间紧密协作:
- 场景:当两个或多个类之间存在紧密的逻辑联系,相互之间需要频繁访问对方的私有成员。例如一个图形绘制系统中,
Circle
类和 Canvas
类,Canvas
类需要访问 Circle
类的私有数据来进行绘制,同时 Circle
类也可能需要访问 Canvas
类的一些私有设置。
减少对封装性负面影响的方法
- 代码结构角度:
- 限制友元声明范围:只在必要的类中声明友元,避免广泛地将友元声明在多个不相关的类中。例如,如果只有一个
Debugger
类需要访问 MyClass
的私有成员,就只在 MyClass
中声明 Debugger
为友元。
- 分组管理友元:如果有多个友元类,可以考虑将它们按照功能或模块进行分组,使得代码结构更清晰。例如,将所有用于调试的友元类放在一个命名空间下,与其他功能的友元类区分开。
- 设计模式角度:
- 使用中介者模式:当多个类之间存在复杂的相互访问关系,可以引入中介者类。例如在上述图形绘制系统中,可以引入一个
GraphicsMediator
类,Circle
类和 Canvas
类通过中介者进行间接交互,减少直接的友元关系,从而在一定程度上维护封装性。
- 依赖倒置原则:尽量依赖抽象而不是具体实现。通过定义抽象接口,让友元类依赖于接口而不是具体类的私有实现细节。这样,即使具体类的私有成员发生变化,对友元类的影响也能降到最低。