重载比较运算符与三路比较:C++20 的 <=> 运算符
说到比较运算符,我最早写 C++ 的时候,最烦的就是给自定义类型写 operator<、operator== 这一套。你想想看,一个结构体有七八个字段,每个比较运算符都得手写一遍,不光累,还容易写错。更别提 operator!= 和 operator== 逻辑相反这种低级 bug 了——嗯,我承认我踩过这个坑。
C++20 引入的 <=> 运算符,说白了就是来救命的。它叫“三路比较运算符”,也有人叫它“太空船运算符”(长得像飞船嘛)。它能一次性搞定所有比较逻辑,编译器还能帮你自动生成剩下的。
三路比较运算符是什么?
先看个最简单的例子:
#include <compare>
#include <iostream>
struct Point {
int x, y;
// C++20 三路比较
auto operator<=>(const Point& other) const = default;
};
int main() {
Point a{1, 2}, b{1, 3};
if (a < b) std::cout << "a < b\n";
if (a == b) std::cout << "a == b\n";
if (a != b) std::cout << "a != b\n";
return 0;
}
看到没?只写了一个 operator<=>,而且加了 = default。编译器自动帮你生成了 <、<=、>、>=、==、!= 这六个运算符。我当年要是赶上 C++20,至少能省下几百行重复代码。
std::strong_ordering、std::weak_ordering、std::partial_ordering。
比较类别类型
这里有个细节我得提醒你。<=> 的返回值类型决定了比较的“严格程度”。我画了张图帮你理清关系:
我个人习惯是:能用 = default 就别手写。编译器生成的逐成员比较,按声明顺序从左到右,遇到第一个不等就返回结果。这跟结构体布局天然一致,不容易出错。
手写三路比较
当然,有时候默认行为不满足需求。比如你只想比较某些字段,或者比较逻辑有特殊规则。这时候就得手写:
struct Student {
std::string name;
int score;
// 只按分数比较,忽略姓名
std::strong_ordering operator<=>(const Student& other) const {
if (auto cmp = score <=> other.score; cmp != 0)
return cmp;
return std::strong_ordering::equal;
}
};
这里我用了 std::strong_ordering 作为返回类型。为什么不用 auto?因为我想明确告诉调用者:这个比较是“可替换相等”的——也就是说,如果两个学生分数相等,它们在逻辑上就是等价的。
<=> 时,可以用 if (auto cmp = ...; cmp != 0) return cmp; 这种写法,逐层比较字段,最后返回 equal。这比嵌套 if-else 清爽多了。
自动生成比较运算符的规则
这里有个坑,我曾经在 code review 时抓到过好几次。编译器自动生成比较运算符是有条件的:
- 所有成员变量都必须支持
<=>比较 - 如果某个成员是数组,数组元素也必须支持
- 基类也必须支持
<=> - 联合体(union)不能自动生成
一旦条件不满足,= default 就会编译失败。这时候你得手写,或者给成员类型补上 <=>。
operator== 或 operator< 中的任何一个,编译器就不会再自动生成对应的运算符了。这是设计上的“你声明了你负责”原则。
实际项目中的经验
我在一个游戏引擎项目里用过三路比较。当时有个 Transform 组件,包含位置、旋转、缩放三个向量。如果用老办法,得写 6 个运算符,每个都要比较三个向量。用 <=> 之后,一行代码搞定:
struct Transform {
Vector3 position;
Quaternion rotation;
Vector3 scale;
auto operator<=>(const Transform&) const = default;
};
而且因为 Vector3 和 Quaternion 本身也支持 <=>,整个比较链是自动展开的。代码量从 60 行缩到了 1 行,可读性还更高了。
不过我也要提醒你:<=> 不是万能的。如果你的比较逻辑涉及外部状态(比如缓存、懒加载),或者比较结果依赖于运行时上下文,那还是老老实实手写比较函数吧。工具再好,也得看场景。
总结一下
| 特性 | 说明 |
|---|---|
<=> 运算符 |
三路比较,返回比较类别类型 |
= default |
自动生成所有比较运算符 |
| 比较类别 | strong / weak / partial,严格程度递减 |
| 手写场景 | 自定义比较逻辑、成员不支持 <=> |
说实话,三路比较是我在 C++20 里最喜欢的特性之一。它把一堆琐碎的重复劳动变成了编译器的工作,让我能更专注于业务逻辑。如果你还在用 C++17 或更早的标准,我建议你尽快升级——光这一个特性就值回票价了。