C++ operator== 重载与比较语义
Coperator重载与比较语义自定义类型默认没有需通过成员或非成员operator或C20的operator/ default定义相等语义。本文归纳常见写法、跨类型比较、与STL / Concepts的关系及易错点。目录为何需要重载成员函数与非成员函数C20默认相等与三路比较只定义A B时会发生什么不写operator能否比较等价关系与工程约定不等运算符、对称性与隐式转换三路比较、constexpr 与 noexcept三路比较的返回类型速查浮点、容差与「数学相等」隐藏友元惯用法继承与多态与智能指针、optional、泛型其它注意参考链接为何需要重载对class/struct未提供时a b无法通过编译除非存在可解释的其它重载见下文。structPoint{intx,y;};// Point a, b; (void)(a b); // 未定义 operator 则编译失败成员函数与非成员函数成员左操作数为*thisstructPoint{intx,y;booloperator(constPointo)const{returnxo.xyo.y;}};非成员常配合友元访问私有成员或仅使用公开接口booloperator(constPointa,constPointb){returna.xb.xa.yb.y;}非成员有利于a b与b a对称在两侧类型不同时常需两个非成员或operator的可逆重载纯成员A::operator(const B)不能解释b a。C20默认相等与三路比较对可逐成员比较的类型可用structPoint{intx,y;booloperator(constPoint)constdefault;};编译器按非静态数据成员生成按字典序的相等比较规则见标准。三路比较structPoint{intx,y;autooperator(constPoint)constdefault;};在 C20 下默认通常会一并提供、!与关系运算符具体生成规则以cppreference / 标准为准。若项目已用C20可优先评估 default是否覆盖需求。写法典型用途仅operator default只需要相等/不等不需要、等全序operator defaultauto成员均可时由编译器推导最强合理比较类别显式返回std::strong_ordering等需要固定类别、或与自定义比较逻辑混用与手写并存可显式default的与手写或反之组合但需避免语义不一致多数场景要么全默认要么关系与相等一并手写并交叉核对。只定义A B时会发生什么classB{public:inty;};classA{intx;std::string s;public:booloperator(constBo)const{returnxo.y;}};a b解析为a.operator(b)仅比较实现里用到的成员上例中s不参与。a1 a2若没有operator(const A)或其它匹配重载编译失败。b a若无B::operator(const A)或非成员operator(const B, const A)编译失败。跨类型比较语义应在注释或类型名中写清是业务等价如与 ID 类型比还是值全等。不写operator能否比较情况A A未定义任何编译错误未使用则不受影响operator delete有意的不可用调用为 ill-formed仅A BA A仍错误C20operator(const A) const default合法C20operator(const A) const default通常提供完整比较族注意即便只有int x;等公开成员也不会像部分语言那样自动按成员比较必须显式默认或手写。等价关系与工程约定对作为值语义使用的类型宜满足自反a a对称a b与b a一致传递若ab且bc则ac用于std::unordered_map的键时还须与std::hash一致相等键必同哈希否则行为错误。不等运算符、对称性与隐式转换C20 前常手写!为!(ab)或与成对实现。C20 起可由反序等形式生成!规则见标准。单参数构造函数若非explicit可能使a 整型等通过B(整型)间接调用A::operator(const B)产生意外匹配建议对可转换构造函数使用explicit。三路比较、constexpr 与 noexcept点说明一套默认关系运算符适合“全序 相等”需求明确的类型。constexpr比较可在编译期执行时将/标为constexpr便于static_assert等。noexcept标准库部分场景假设比较不抛异常内抛异常易破坏异常安全与算法前提建议noexcept在可行时标明。三路比较的返回类型速查返回类型含义概括典型场景std::strong_ordering全序 可替换ab则处处可互换整数、指针地址序、string默认比较等std::weak_ordering全序但不一定表示逐成员全同只想排序、或「业务相等」弱于「表示相等」std::partial_ordering可能存在不可比如NaN参与浮点比较封装double且保留 IEEE 语义时strong_orderingweak_orderingpartial_ordering箭头表示可隐式转换到更弱的类别为 API 选择返回类型时宁强勿弱能strong就不要用weak除非语义确实需要。浮点、容差与「数学相等」事实建议double上受舍入影响连续数学意义上的「相等」应用容差或ULP比较而不是裸NaNNaN NaN为假不满足自反与「等价关系」定义冲突若把浮点当键或哈希需单独约定-0.0与0.0为真但std::signbit不同序列化/哈希是否要区分看业务封装「可哈希、可排序」的浮点类型时常显式选择要么严格 IEEE partial_ordering要么定义非标准的strong/weak比较并在文档中写清与的关系。隐藏友元惯用法英文资料中常称Hidden friend惯用法。将operator及operator写成类内friend非成员函数可兼顾对称重载解析与尽量少泄漏到 enclosing scopestructPoint{intx,y;friendconstexprbooloperator(constPointa,constPointb)default;friendconstexprautooperator(constPoint,constPoint)default;};ADL仅当某实参类型为Point时参与候选减少无关类型的意外匹配。与非成员非友元相比访问私有成员更方便与纯成员相比更易写出a b与b a对称尤其跨类型时配合第二个friend重载。继承与多态对多态基类用const Base const Base且按值比较派生成员时易发生对象切片或类型混淆。常见做法包括相等性主要在具体派生类型上定义或提供虚函数equals等注意对称性与const设计成本较高。基类是否提供operator需与整体 OOP 设计一致无万能模板。与智能指针、optional、泛型类型含义常见std::optionalA有值/无值规则 有值时调用A的std::unique_ptrT默认比较指针地址是否指向同一对象不自动比较*pstd::shared_ptrT同理多为控制块/指针地址比较与“指向内容相等”不同内容相等在确认非空后使用*p1 *p2C20Concepts如std::equality_comparable_with等会约束的返回类型与可用性写库代码时需注意。其它注意值语义 vs 标识语义少数场景用this other表示同一实体与常规值相等不同需在文档中区分。ABI / 布局变更增删成员后若手写未同步更新可能导致旧动态库与新对象布局混用风险 default可降低遗漏。跨模块边界比较逻辑与对象布局、序列化格式宜一并版本化。仅相等A 与 B对称需要哪些比较C20: 考虑 default 或 default成员 A::B 或非成员 成对声明优先非成员 或 双向非成员参考链接Operator overloading - cppreference.comDefault comparisons (C20) - cppreference.comstd::equality_comparable - cppreference.com具体生成规则与编译器诊断以所用C 标准版本为准。