C++添加了<=>运算符,当使用 a <=> b
时,它返回的不是一个简单的布尔值 (true
/false
) 或整数。
它返回一个特殊的比较类别对象 (comparison category object)。这个对象封装了 a
和 b
之间详细的排序关系。这些对象的类型都定义在 <compare>
头文件中。
最核心的返回类型有三种:
std::strong_ordering
(强有序)std::weak_ordering
(弱有序)std::partial_ordering
(偏序)
理解返回对象的本质:与 0 比较
理解这些返回对象最简单的方式,就是把它们想象成一个“黑盒”,你可以拿它和 0
进行比较。这个思想继承自C语言中 strcmp
等函数的设计。
a <=> b
的结果 res
可以这样来解读:
- 如果
res < 0
,意味着a < b
- 如果
res > 0
,意味着a > b
- 如果
res == 0
,意味着a
和b
相等或等价
这就是为什么编译器可以基于 a <=> b
的结果自动生成 a < b
((a <=> b) < 0
)、a > b
((a <=> b) > 0
) 等操作的原因。
三种返回类型的详细说明
那么,为什么需要三种不同的类型呢?因为它们描述了不同层次的“相等”关系。
1. std::strong_ordering
(强有序)
这是最严格、最常见的排序。
- 核心思想:如果
a
和b
的比较结果是“相等”,那么它们就是完全等同、可以互相替换的。对a
做任何操作的结果都应该和对b
做同样操作的结果完全一样。 - 返回的具名常量:
std::strong_ordering::less
(表示a < b
)std::strong_ordering::equal
(表示a
和b
完全相等)std::strong_ordering::greater
(表示a > b
)
- 典型例子:
int
类型:5 <=> 10
返回std::strong_ordering::less
。- 只包含整型成员的结构体。
#include <compare>
#include <iostream>
int main() {
int a = 5, b = 10;
std::strong_ordering result = (a <=> b);
if (result == std::strong_ordering::less) {
std::cout << "a is less than b" << std::endl;
}
}
2. std::weak_ordering
(弱有序)
- 核心思想:如果
a
和b
的比较结果是“相等”,它们仅仅是排序等价 (equivalent),但本身不一定完全相同。 - 返回的具名常量:
std::weak_ordering::less
std::weak_ordering::equivalent
(表示a
和b
排序等价)std::weak_ordering::greater
- 典型例子:
- 不区分大小写的字符串比较:字符串
"apple"
和"Apple"
在不区分大小写的排序中是“等价”的,但它们本身并不相等。 - 一个只关心部分成员进行比较的类。
- 不区分大小写的字符串比较:字符串
// 伪代码示例
// CaseInsensitiveString s1 = "apple";
// CaseInsensitiveString s2 = "Apple";
// std::weak_ordering result = (s1 <=> s2); // result 会是 std::weak_ordering::equivalent
3. std::partial_ordering
(偏序)
- 核心思想:它除了描述大小关系,还允许“无法比较 (unordered)”这种情况的存在。
- 返回的具名常量:
std::partial_ordering::less
std::partial_ordering::equivalent
std::partial_ordering::greater
std::partial_ordering::unordered
(表示a
和b
无法比较)
- 典型例子:
- 浮点数
double
或float
:因为有NaN
(Not a Number) 的存在。任何数字与NaN
的比较结果都是“无序”的。 1.0 <=> 2.0
返回std::partial_ordering::less
。1.0 <=> NAN
返回std::partial_ordering::unordered
。
- 浮点数
使用宇宙飞船运算符重载运算符
对于大多数简单的结构体或类,你甚至不需要自己实现 operator<=>
的函数体。你只需要告诉编译器使用默认版本
编译器会按照成员变量的声明顺序,依次对每个成员进行三路比较,一旦发现不相等就立即返回结果。
**示例:使用 default
的 Point
类
#include <iostream>
#include <compare> // 引入比较类别
class Point {
public:
int x, y;
// 魔法发生的地方!
// 1. 默认生成三路比较,它会依次比较 x 和 y
auto operator<=>(const Point& other) const = default;
// 2. 默认生成相等比较(通常也建议默认化)
bool operator==(const Point& other) const = default;
};
int main() {
Point p1{1, 2};
Point p2{1, 3};
Point p3{2, 1};
std::cout << std::boolalpha; // 让输出显示 true/false
// 所有这些比较现在都可以直接工作了!
std::cout << "p1 < p2: " << (p1 < p2) << std::endl; // true
std::cout << "p1 > p2: " << (p1 > p2) << std::endl; // false
std::cout << "p1 == p1: " << (p1 == p1) << std::endl; // true
std::cout << "p1 != p2: " << (p1 != p2) << std::endl; // true
std::cout << "p3 >= p1: " << (p3 >= p1) << std::endl; // true
}
编译器如何决定返回哪种类型?
当你为一个类使用 auto operator<=>(...) const = default;
时,编译器会检查类的所有成员:
- 如果所有成员的
operator<=>
都返回std::strong_ordering
,那么最终结果就是std::strong_ordering
。 - 如果成员中至少有一个返回
std::weak_ordering
(且没有返回partial_ordering
的),那么最终结果就是std::weak_ordering
。 - 如果成员中至少有一个返回
std::partial_ordering
,那么最终结果就是std::partial_ordering
。
这就像一个“最弱一环”原则,返回类型由最不严格的那个成员比较来决定。auto
关键字在这里非常关键,它让编译器为你自动推断出正确的返回类型。
总结
返回类型 | 核心含义 | "相等"的常量 | "无序"的可能 | 典型用例 |
---|---|---|---|---|
std::strong_ordering |
完全相同,可替换 | equal |
否 | int , std::string |
std::weak_ordering |
排序上等价,但本身不一定相同 | equivalent |
否 | 不区分大小写的字符串 |
std::partial_ordering |
可能存在无法比较的情况 | equivalent |
是 (unordered ) |
double , float (因NaN ) |
所以,a <=> b
返回的是一个信息丰富的类别对象,不仅返回你比较结果,还返回这个比较的性质(是强有序、弱有序还是偏序)。