重载运算符必须保持内置语义:operator+应返回新对象而非修改左操作数,operator==须满足自反、对称、传递性;前置++返回引用,后置++返回副本;流操作符和可交换运算符需用非成员函数(常为friend)实现。

重载运算符必须保持内置语义
运算符重载不是起新名字,而是延续已有含义。比如 operator+ 就该做“合并”或“生成新对象”,不能偷偷修改左操作数;operator== 必须满足自反性、对称性、传递性,否则 std::set 或 std::unordered_map 会行为异常。一旦打破这些隐含契约,调用方根本不会怀疑是你重载错了——它只会认为“这容器坏了”。
别让 operator+ 做 operator+= 的事
常见错误是把加法写成就地修改:
String& String::operator+(const String& rhs) {
m_data.append(rhs.m_data); // 错!这是 += 行为
return *this;
}
这会导致 a + b 改变 a,违背加法的数学直觉。正确做法是返回新对象:
String String::operator+(const String& rhs) const {
String result(*this);
result.m_data.append(rhs.m_data);
return result;
}
- 返回值类型应为值类型(非引用),除非你明确想支持链式赋值(如
+=) - 成员函数加
const限定符,强调不修改当前对象 - 如果涉及隐式转换(比如
String + const char*),用非成员函数更安全
前置/后置自增要严格区分返回值
++i 和 i++ 语义完全不同:前者应返回引用,后者必须返回副本。混用会导致临时对象被取地址、迭代器失效等隐蔽问题:
立即学习“C++免费学习笔记(深入)”;
Counter& Counter::operator++() { // 前置
++m_value;
return *this;
}
Counter Counter::operator++(int) { // 后置,int 是占位参数
Counter old = *this;
++(*this);
return old;
}
如果后置版本误返回 Counter&,编译可能通过,但返回的是局部对象引用,一出作用域就悬空。
流操作符必须用非成员函数重载
operator<< 和 operator>> 左操作数是 std::ostream 或 std::istream,不是你的类。强行塞进成员函数会导致左操作数无法隐式转换(比如你想支持 cout << obj,但 obj.operator<<(cout) 把 cout 当右操作数,逻辑反了):
friend std::ostream& operator<<(std::ostream& os, const Date& d) {
return os << d.m_year << "-" << d.m_month << "-" << d.m_day;
}
这个细节漏掉,<< 看起来能用,但一旦涉及 std::ostringstream 或自定义流缓冲区,就会因类型不匹配而静默失败。
最常被忽略的是“是否可交换”:像 +、== 这类运算符,如果只提供成员版本,123 + obj 就无法触发隐式转换(int 不能自动转成你的类)。这时候必须补一个非成员版本,且通常得是 friend 才能访问私有成员。


















