简介

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);
}
};

这种方式存在以下问题:

  1. 代码冗余,需要实现多个相似的运算符
  2. 容易出错,运算符之间的一致性难以保证
  3. 维护成本高,修改比较逻辑需要多处修改

三路比较运算符

基本概念

三路比较运算符 <=> 返回一个比较结果类型,该类型可以转换为三种状态:

  • 小于(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 意味着 ab 在所有上下文中可以互换
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::less
  • std::strong_ordering::equal
  • std::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::less
  • std::weak_ordering::equivalent
  • std::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::less
  • std::partial_ordering::equivalent
  • std::partial_ordering::greater
  • std::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;
};

// 编译器自动生成:
// operator!=
// operator<
// operator<=
// operator>
// operator>=

实际示例

示例 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'; // true
std::cout << "p1 < p2: " << (p1 < p2) << '\n'; // false
std::cout << "p1 > p2: " << (p1 > p2) << '\n'; // true

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;
}

注意事项

  1. 一致性<=>== 的实现必须保持一致
  2. 传递性:比较关系必须满足传递性
  3. 对称性a <=> b 的结果应该与 -(b <=> a) 一致
  4. 性能:对于复杂类型,手动实现可能比默认实现更高效

总结

三路比较运算符是 C++20 的一个重要特性,它:

  • 简化了比较运算符的实现
  • 减少了代码冗余
  • 提高了代码的可维护性
  • 与标准库完美集成

通过使用 <=>,我们可以用更少的代码实现更清晰、更安全的比较逻辑。