当前位置: 首页 > news >正文

C++ operator== 重载与比较语义

C++operator==重载与比较语义

自定义类型默认没有==;需通过成员或非成员operator==(或C++20operator<=>/= default)定义相等语义。本文归纳常见写法、跨类型比较、与STL / Concepts的关系及易错点。

目录

  • 为何需要重载
  • 成员函数与非成员函数
  • C++20:默认相等与三路比较
  • 只定义A == B时会发生什么
  • 不写operator==能否比较
  • 等价关系与工程约定
  • 不等运算符、对称性与隐式转换
  • 三路比较、constexpr 与 noexcept
  • 三路比较的返回类型(速查)
  • 浮点、容差与「数学相等」
  • 隐藏友元惯用法
  • 继承与多态
  • 与智能指针、optional、泛型
  • 其它注意
  • 参考链接

为何需要重载

class/struct未提供==时,a == b无法通过编译(除非存在可解释的其它重载,见下文)。

structPoint{intx,y;};// Point a, b; (void)(a == b); // 未定义 operator== 则编译失败

成员函数与非成员函数

成员(左操作数为*this):

structPoint{intx,y;booloperator==(constPoint&o)const{returnx==o.x&&y==o.y;}};

非成员(常配合友元访问私有成员,或仅使用公开接口):

booloperator==(constPoint&a,constPoint&b){returna.x==b.x&&a.y==b.y;}

非成员有利于a == bb == a对称(在两侧类型不同时,常需两个非成员或operator==的可逆重载);纯成员A::operator==(const B&)不能解释b == a


C++20:默认相等与三路比较

可逐成员比较的类型,可用:

structPoint{intx,y;booloperator==(constPoint&)const=default;};

编译器按非静态数据成员生成按字典序的相等比较(规则见标准)。

三路比较

structPoint{intx,y;autooperator<=>(constPoint&)const=default;};

在 C++20 下,默认<=>通常会一并提供==!=与关系运算符(具体生成规则以cppreference / 标准为准)。若项目已用C++20,可优先评估<=>= default是否覆盖需求。

写法典型用途
operator== = default只需要相等/不等,不需要<>等全序
operator<=> = defaultauto成员均可<=>时,由编译器推导最强合理比较类别
显式返回std::strong_ordering需要固定类别、或与自定义比较逻辑混用

与手写==并存:可显式default==与手写<=>(或反之)组合,但需避免语义不一致;多数场景要么全默认,要么关系与相等一并手写并交叉核对。


只定义A == B时会发生什么

classB{public:inty;};classA{intx;std::string s;public:booloperator==(constB&o)const{returnx==o.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仍错误
C++20operator==(const A&) const = default合法
C++20operator<=>(const A&) const = default通常提供完整比较族

注意:即便只有int x;等公开成员,也不会像部分语言那样自动按成员比较;必须显式默认或手写。


等价关系与工程约定

对作为值语义使用的类型,==宜满足:

  • 自反a == a
  • 对称a == bb == a一致
  • 传递:若a==bb==ca==c

用于std::unordered_map的键时,还须与std::hash一致:相等键必同哈希(否则行为错误)。


不等运算符、对称性与隐式转换

  • C++20 前:常手写!=!(a==b)或与==成对实现。
  • C++20 起:可由==反序等形式生成!=(规则见标准)。

单参数构造函数若非explicit,可能使a == 整型等通过B(整型)间接调用A::operator==(const B&),产生意外匹配;建议对可转换构造函数使用explicit


三路比较、constexpr 与 noexcept

说明
<=>一套默认关系运算符;适合“全序 + 相等”需求明确的类型。
constexpr比较可在编译期执行时,将==/<=>标为constexpr,便于static_assert等。
noexcept标准库部分场景假设比较不抛异常==内抛异常易破坏异常安全与算法前提,建议noexcept在可行时标明

三路比较的返回类型(速查)

返回类型含义(概括)典型场景
std::strong_ordering全序 +可替换a==b则处处可互换)整数、指针地址序、string默认比较等
std::weak_ordering全序,但==不一定表示逐成员全同只想排序、或「业务相等」弱于「表示相等」
std::partial_ordering可能存在不可比(如NaN参与浮点比较)封装double且保留 IEEE 语义时

strong_ordering

weak_ordering

partial_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 scope

structPoint{intx,y;friendconstexprbooloperator==(constPoint&a,constPoint&b)=default;friendconstexprautooperator<=>(constPoint&,constPoint&)=default;};
  • ADL:仅当某实参类型为Point时参与候选,减少无关类型的意外匹配。
  • 非成员非友元相比,访问私有成员更方便;与纯成员相比,更易写出a == bb == a对称(尤其跨类型时配合第二个friend重载)。

继承与多态

多态基类const Base& == const Base&且按值比较派生成员时,易发生对象切片类型混淆。常见做法包括:

  • 相等性主要在具体派生类型上定义;或
  • 提供虚函数equals等(注意对称性与const),设计成本较高。

基类是否提供operator==需与整体 OOP 设计一致,无万能模板。


与智能指针、optional、泛型

类型==含义(常见)
std::optional<A>有值/无值规则 + 有值时调用A==
std::unique_ptr<T>默认比较指针地址(是否指向同一对象),自动比较*p
std::shared_ptr<T>同理,多为控制块/指针地址比较;与“指向内容相等”不同
内容相等在确认非空后使用*p1 == *p2

C++20Conceptsstd::equality_comparable_with等会约束==的返回类型与可用性;写库代码时需注意。


其它注意

  • 值语义 vs 标识语义:少数场景用this == &other表示同一实体;与常规值相等不同,需在文档中区分。
  • ABI / 布局变更:增删成员后若手写==未同步更新,可能导致旧动态库与新对象布局混用风险;= default可降低遗漏。
  • 跨模块边界:比较逻辑与对象布局、序列化格式宜一并版本化。

仅相等

A 与 B

对称

需要哪些比较?

C++20: 考虑 == default 或 <=> default

成员 A::==B 或非成员 成对声明

优先非成员 或 双向非成员


参考链接

  • Operator overloading - cppreference.com
  • Default comparisons (C++20) - cppreference.com
  • std::equality_comparable - cppreference.com

具体生成规则与编译器诊断以所用C++ 标准版本为准。

http://www.jsqmd.com/news/551853/

相关文章:

  • 5个高效配置让Dev-CPP成为C/C++编程入门利器
  • 从‘量子电子商务’到三方协议:手把手拆解量子数字签名(QDS)的核心流程与实验挑战
  • RexUniNLU在Java面试题自动生成中的应用
  • uniapp安卓应用实现开机自启动的完整配置指南
  • Magisk Root权限管理:5步掌握Android系统自定义核心技术
  • 告别编译烦恼:在Ubuntu 22.04上快速验证OpenCV 3.4.15安装的几种方法
  • HarmonyOS6 半年磨一剑 - RcTextarea 组件样式系统与边框模式深度剖析
  • 智能家庭网络系统新选择:iStoreOS打造高效家庭网络与存储中心
  • Python高级特性详解:从基础到进阶
  • ArcGIS里算的面积总对不上?可能是你的投影和单位没搞懂(附模型构建器解决方案)
  • Powershell创建ISO文件全攻略:从基础命令到高级参数详解
  • 我爱学算法之——动态规划(一)
  • 给嵌入式新手的ST7789驱动避坑指南:从SPI模式到RGB565显示的完整配置流程
  • Aspen Plus助力费托工艺尾气转化:从CO₂到合成气的奇妙之旅
  • 如何快速掌握SMU Debug Tool:AMD Ryzen性能调试终极指南
  • GMSL GUI实战:利用EOM眼图与Link Margin优化高速链路设计
  • 人大金仓KingBaseES数据库迁移实战:从SQLServer到国产数据库的避坑指南
  • 鸿蒙智能车实战:基于HI3861与QT的远程控制与数据可视化系统设计
  • 革新性游戏增强工具:植物大战僵尸智能辅助套件
  • 从零到一:STM32F407 HAL库定时器中断精准点亮LED(CubeMX实战)
  • KKS-HF_Patch:让《Koikatsu Sunshine》焕发全新光彩的三大核心功能
  • 循环队列的5个经典面试题解析(附C语言实现代码)
  • 新手入门指南:零基础使用快马AI生成你的第一张产区标准示意图
  • 手机上的3D视觉革命:拆解iPhone结构光与安卓TOF的AR应用差异
  • 免费音频转录神器oTranscribe:记者学者的终极效率工具
  • 【跟韩工学Ubuntu第7课】-第7章 日志管理:rsyslog、journald与logrotate-002篇
  • 2021 年 3 月青少年软编等考 C 语言三级真题解析
  • OpCore-Simplify:革新黑苹果EFI配置流程的智能解决方案
  • Cosmos-Reason1-7B模型微调实战:基于领域数据提升专业问答效果
  • qt项目如何打包成exe