简介
C++20 引入了三路比较运算符 <=>,也被称为"宇宙飞船运算符"(spaceship operator)。这个运算符可以一次性确定两个对象之间的所有比较关系(小于、等于、大于),大大简化了比较运算符的实现。
传统比较运算符的问题
在 C++20 之前,如果我们想为一个自定义类型实现完整的比较运算符,需要分别实现六个运算符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Point { public: int x, y; bool operator==(const Point& other) const { return x == other.x && y == other.y; } bool operator!=(const Point& other) const { return !(*this == other); } bool operator<(const Point& other) const { if (x != other.x) return x < other.x; return y < other.y; } bool operator<=(const Point& other) const { return !(other < *this); } bool operator>(const Point& other) const { return other < *this; } bool operator>=(const Point& other) const { return !(*this < other); } };
|
这种方式存在以下问题:
- 代码冗余,需要实现多个相似的运算符
- 容易出错,运算符之间的一致性难以保证
- 维护成本高,修改比较逻辑需要多处修改
三路比较运算符
基本概念
三路比较运算符 <=> 返回一个比较结果类型,该类型可以转换为三种状态:
- 小于(less)
- 等于(equal)
- 大于(greater)
比较结果类型
<=> 返回的类型是 std::compare_three_way_result_t<T>,通常是以下之一:
std::strong_ordering:强序std::weak_ordering:弱序std::partial_ordering:偏序
强序(strong_ordering)
强序是最严格的排序,要求:
- 等价性意味着可替换性(equivalence implies substitutability)
a == b 意味着 a 和 b 在所有上下文中可以互换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <compare>
class Point { public: int x, y; auto operator<=>(const Point&) const = default; std::strong_ordering operator<=>(const Point& other) const { if (auto cmp = x <=> other.x; cmp != 0) return cmp; return y <=> other.y; } };
|
std::strong_ordering 有三个可能的值:
std::strong_ordering::lessstd::strong_ordering::equalstd::strong_ordering::greater
弱序(weak_ordering)
弱序允许等价的值在所有方面不完全相同,但在排序时被视为相等。
1 2 3 4 5 6 7 8 9 10 11
| class CaseInsensitiveString { public: std::string str; std::weak_ordering operator<=>(const CaseInsensitiveString& other) const { auto cmp = strcasecmp(str.c_str(), other.str.c_str()); if (cmp < 0) return std::weak_ordering::less; if (cmp > 0) return std::weak_ordering::greater; return std::weak_ordering::equivalent; } };
|
std::weak_ordering 有三个可能的值:
std::weak_ordering::lessstd::weak_ordering::equivalentstd::weak_ordering::greater
偏序(partial_ordering)
偏序允许某些值之间无法比较(如 NaN)。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class FloatWrapper { public: float value; std::partial_ordering operator<=>(const FloatWrapper& other) const { if (std::isnan(value) || std::isnan(other.value)) { return std::partial_ordering::unordered; } if (value < other.value) return std::partial_ordering::less; if (value > other.value) return std::partial_ordering::greater; return std::partial_ordering::equivalent; } };
|
std::partial_ordering 有四个可能的值:
std::partial_ordering::lessstd::partial_ordering::equivalentstd::partial_ordering::greaterstd::partial_ordering::unordered
默认实现
C++20 允许使用 = default 来自动生成比较运算符:
1 2 3 4 5 6 7 8
| class Point { public: int x, y; auto operator<=>(const Point&) const = default; bool operator==(const Point&) const = default; };
|
编译器会按照成员声明顺序逐个比较成员,生成合适的比较逻辑。
自动生成的比较运算符
当你定义了 <=> 和 == 后,编译器会自动生成其他比较运算符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Point { public: int x, y; auto operator<=>(const Point&) const = default; bool operator==(const Point&) const = default; };
|
实际示例
示例 1:简单的结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <compare> #include <iostream> #include <string>
struct Person { std::string name; int age; auto operator<=>(const Person&) const = default; };
int main() { Person p1{"Alice", 30}; Person p2{"Bob", 25}; Person p3{"Alice", 30}; std::cout << std::boolalpha; std::cout << "p1 == p3: " << (p1 == p3) << '\n'; std::cout << "p1 < p2: " << (p1 < p2) << '\n'; std::cout << "p1 > p2: " << (p1 > p2) << '\n'; return 0; }
|
示例 2:自定义比较逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <compare> #include <string>
class Version { public: int major, minor, patch; std::strong_ordering operator<=>(const Version& other) const { if (auto cmp = major <=> other.major; cmp != 0) return cmp; if (auto cmp = minor <=> other.minor; cmp != 0) return cmp; return patch <=> other.patch; } bool operator==(const Version& other) const { return major == other.major && minor == other.minor && patch == other.patch; } };
|
示例 3:处理不可比较的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <compare> #include <cmath>
class SafeFloat { public: float value; std::partial_ordering operator<=>(const SafeFloat& other) const { if (std::isnan(value) || std::isnan(other.value)) { return std::partial_ordering::unordered; } return value <=> other.value; } bool operator==(const SafeFloat& other) const { return value == other.value; } };
|
与标准库的集成
三路比较运算符与标准库容器和算法完美集成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <compare> #include <set> #include <vector> #include <algorithm>
struct Point { int x, y; auto operator<=>(const Point&) const = default; };
int main() { std::set<Point> points; points.insert({1, 2}); points.insert({3, 4}); points.insert({1, 2}); std::vector<Point> vec = {{3, 1}, {1, 2}, {2, 3}}; std::sort(vec.begin(), vec.end()); return 0; }
|
注意事项
- 一致性:
<=> 和 == 的实现必须保持一致 - 传递性:比较关系必须满足传递性
- 对称性:
a <=> b 的结果应该与 -(b <=> a) 一致 - 性能:对于复杂类型,手动实现可能比默认实现更高效
总结
三路比较运算符是 C++20 的一个重要特性,它:
- 简化了比较运算符的实现
- 减少了代码冗余
- 提高了代码的可维护性
- 与标准库完美集成
通过使用 <=>,我们可以用更少的代码实现更清晰、更安全的比较逻辑。