C++ 三路比较 (Three-way Comparison)
简介
C++20 引入了三路比较运算符 <=>,也被称为"宇宙飞船运算符"(spaceship operator)。这个运算符可以一次性确定两个对象之间的所有比较关系(小于、等于、大于),大大简化了比较运算符的实现。
传统比较运算符的问题
在 C++20 之前,如果我们想为一个自定义类型实现完整的比较运算符,需要分别实现六个运算符:
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在所有上下文中可以互换
#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)
弱序允许等价的值在所有方面不完全相同,但在排序时被视为相等。
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)。
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 来自动生成比较运算符:
class Point {
public:
int x, y;
// 自动生成所有六个比较运算符
auto operator<=>(const Point&) const = default;
bool operator==(const Point&) const = default;
};编译器会按照成员声明顺序逐个比较成员,生成合适的比较逻辑。
自动生成的比较运算符
当你定义了 <=> 和 == 后,编译器会自动生成其他比较运算符:
class Point {
public:
int x, y;
auto operator<=>(const Point&) const = default;
bool operator==(const Point&) const = default;
};
// 编译器自动生成:
// operator!=
// operator<
// operator<=
// operator>
// operator>=实际示例
示例 1:简单的结构体
#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'; // true
std::cout << "p1 < p2: " << (p1 < p2) << '\n'; // false
std::cout << "p1 > p2: " << (p1 > p2) << '\n'; // true
return 0;
}示例 2:自定义比较逻辑
#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:处理不可比较的值
#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;
}
};与标准库的集成
三路比较运算符与标准库容器和算法完美集成:
#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 的一个重要特性,它:
- 简化了比较运算符的实现
- 减少了代码冗余
- 提高了代码的可维护性
- 与标准库完美集成
通过使用 <=>,我们可以用更少的代码实现更清晰、更安全的比较逻辑。











