运算符重载与友元函数
众所周知,运算符重载作为多态的一种实现,使用是较为普遍的
但是重载运算符,本身是几元就有几个参数,对于二元运算符,第一个参数对应左侧运算对象,第二个参数对应右侧运算对象
而重载运算符作为类的成员函数的时候,第一个参数就隐式绑定了this指针,即左侧运算对象固定为this指针
举个最常见的拷贝赋值重载函数例子
1 | class DeepCopy |
但是,我们难免会遇到非成员函数重载的情况,此时我们就要借助友元函数或者全局函数,但友元函数相对更为常规,比如以下例子
1. 需要对称性或隐式转换支持
- 场景:当运算符的两个操作数需要平等对待(如
+
、*
、==
等双目运算符),且可能涉及不同类型的隐式转换。 - 原因:成员函数将左侧操作数(
this
)固定为类类型,限制了左侧操作数的隐式转换。而非成员函数允许左右操作数都进行隐式转换,提供更自然的语义。 - 示例:如果
1
2
3
4
5
6
7
8
9
10
11class MyClass {
int value;
public:
MyClass(int v) : value(v) {}
friend MyClass operator+(const MyClass& lhs, const MyClass& rhs) {
return MyClass(lhs.value + rhs.value);
}
};
MyClass a(5), b(10);
MyClass c = a + b; // OK
MyClass d = 5 + a; // 非成员函数支持 int 到 MyClass 的隐式转换operator+
是成员函数,5 + a
会失败,因为左侧的int
无法调用成员函数。
2. 涉及非类类型或标准库类型
- 场景:运算符涉及自定义类与内置类型(如
int
、double
)或标准库类型(如std::string
)的交互。 - 原因:你无法修改内置类型或标准库类型的定义来添加成员函数,因此只能使用非成员函数。
- 示例:
1
2
3
4
5
6
7
8
9class MyClass {
std::string data;
public:
MyClass(const std::string& d) : data(d) {}
friend std::string operator+(const std::string& lhs, const MyClass& rhs) {
return lhs + rhs.data;
}
};
std::string s = "Hello, " + MyClass("World"); // 非成员函数支持
3. 提高封装性或避免侵入式修改
- 场景:当你不希望修改类的定义(例如第三方库的类)或想保持类接口简洁。
- 原因:非成员函数(通常声明为
friend
或通过公共接口访问)可以在类外部定义,减少对类内部实现的依赖。 - 示例:这里无需修改
1
2
3
4
5
6
7
8
9class MyClass {
int value;
public:
MyClass(int v) : value(v) {}
int getValue() const { return value; }
};
MyClass operator+(const MyClass& lhs, const MyClass& rhs) {
return MyClass(lhs.getValue() + rhs.getValue());
}MyClass
的定义,保持了封装性。
4. 流运算符(<<
和 >>
)
- 场景:重载输入输出流运算符(如
std::ostream
的<<
或std::istream
的>>
)。 - 原因:这些运算符的左侧操作数是
std::ostream
或std::istream
,无法修改其类定义,因此必须使用非成员函数。 - 示例:
1
2
3
4
5
6
7
8
9
10
11class MyClass {
int value;
public:
MyClass(int v) : value(v) {}
friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
os << obj.value;
return os;
}
};
MyClass x(5);
std::cout << x; // 输出 5
注意事项
- 友元 vs. 公共接口:非成员函数可以通过
friend
访问私有成员,或通过公共 getter 方法操作。前者更简洁但可能降低封装性,后者更符合封装但代码稍冗长。 - 隐式转换的副作用:非成员函数支持双边隐式转换,可能导致意外的类型转换,需谨慎设计构造函数(考虑
explicit
)。 - 性能:非成员函数通常是内联的(
inline
),性能与成员函数相当,但复杂运算需注意临时对象的创建和销毁开销。
总结
建议将运算符重载定义为非成员函数的场景包括:
- 需要左右操作数的对称性或隐式转换(如
+
、*
)。 - 涉及非类类型或标准库类型(如
std::string
、std::ostream
)。 - 提高封装性或避免修改类定义。
- 流运算符(
<<
、>>
)。
关于友元的相关介绍,可以查看另一篇帖子。
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Comments