Featured image of post C++20 三路比较运算符(宇宙飞船运算符)使用指南

C++20 三路比较运算符(宇宙飞船运算符)使用指南

解析C++20三路比较运算符(spaceship operator)的原理、用法和最佳实践。通过实例对比C++20前后的实现差异,掌握现代C++比较运算符的高效使用方法。

C++20 引入的三路比较运算符(three-way comparison operator),也被称为"宇宙飞船运算符"(spaceship operator),写作 <=>。用于简化和统一比较操作。

基本概念

<=> 运算符一次性完成两个值的比较,返回三种可能的结果:

  • 小于(less)- (a <=> b) < 0 if a < b
  • 等于(equal)- (a <=> b) == 0 if a and b are equal/equivalent.
  • 大于(greater)- (a <=> b) > 0 if a > b

返回类型

它返回一个表示比较结果的值,可以直接用于排序和相等性判断。

<=> 返回以下三种类型之一:

  1. std::strong_ordering - 强排序

    • 值:less, equal, greater
    • 适用于:整数、指针等
  2. std::weak_ordering - 弱排序

    • 值:less, equivalent, greater
    • 适用于:大小写不敏感的字符串比较
  3. std::partial_ordering - 偏序

    • 值:less, equivalent, greater, unordered
    • 适用于:浮点数(因为有 NaN)

动机

当第一次接触到<=> 运算符时,大概会比较困惑,它的作用到底是什么?使用场景又是什么?等等一系列问题。

在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
30
31
32
33
34
35
36
struct Point {
    int x, y;
    
    bool operator==(const Point& rhs) const {
        return x == rhs.x && y == rhs.y;
    }

    bool operator!=(const Point& rhs) const {
        return !(*this == rhs);
    }
    
    bool operator<(const Point& rhs) const {
        if (x != rhs.x)
            return x < rhs.x;
        return y < rhs.y;
    }
    
    bool operator<=(const Point& rhs) const {
        return !(rhs < *this);
    }
    
    bool operator>(const Point& rhs) const {
        return rhs < *this;
    }
    
    bool operator>=(const Point& rhs) const {
        return !(*this < rhs);
    }
};

int main() {
    Point p1{1, 2}, p2{1, 3};

    std::cout << std::boolalpha << (p1 < p2) << '\n'; // true
    std::cout << std::boolalpha << (p1 == p2) << "\n"; // false
}

即,需要实现 6 个比较运算符,才能进行自定义类型的比较。而定义一个 <=> 运算符后,编译器会自动生成所有六个比较运算符(<, <=, >, >=, ==, !=

基本用法

基本类型比较:

1
2
3
4
5
6
7
8
int main() {
    int a = 5, b = 10;
    std::strong_ordering result = a <=> b;
    
    if (result < 0) {
        std::cout << "a < b\n";
    }
}

自定义类型比较:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <compare>
#include <iostream>

struct Point {
    int x, y;
    
    // 只需定义 <=> 运算符
    auto operator<=>(const Point& other) const = default;
};

int main() {
    Point p1{1, 2}, p2{1, 3};
    
    if (p1 < p2) {
        std::cout << "p1 < p2\n";
    }
    
    return 0;
}

自定义比较逻辑 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct Person  
{  
    std::string name;  
    int age;  
  
    // 先比较年龄,相同再按姓名  
    auto operator<=>(const Person& rhs) const  
    {  
        if (auto cmp = age <=> rhs.age; cmp != 0)  
            return cmp;  
        return name <=> rhs.name;  
    }  
  
    // 仍需显式定义 ==    
    bool operator==(const Person& rhs) const = default;  
};

Person p1{"Alice", 30}, p2{"Bob", 25};  
std::cout << std::boolalpha << (p1 >= p2) << '\n'; // true (30 > 25)
std::cout << std::boolalpha << (p1 == p2) << '\n'; // false (30 != 25)

[!note] 当自定义了 operator<=> 后,需要显示定义 opretor== 运算符,因为 operator<=> 不会自动生成 operator==,主要是为了性能考虑,<=> 可能做了不必要的排序比较,而 == 只需要判断相等即可。

官方解释:

类型有了 <=> 之后,== 是从 <=> 生成的。对于字符串,== 通常通过首先比较大小来优化:如果字符数不同,则字符串不相等。从 <=> 生成的 == 则必须读取足够的字符串以确定它们的词典顺序,那开销就会大得多了。经过长时间的讨论,我们决定不从 <=> 生成 ==。 - 源自《C++ 白皮书》

总结

  1. 简化代码:定义一个 <=> 运算符后,编译器自动生成所有六个比较运算符(<, <=, >, >=, ==, !=

  2. 性能优化:一次比较得到完整的排序关系,避免多次比较

  3. 默认实现:使用 = default 可以让编译器自动生成按成员字典序比较的实现

https://www.stroustrup.com/hopl20main-p5-p-bfc9cd4--final.pdf

https://github.com/Cpp-Club/Cxx_HOPL4_zh

Licensed under CC BY-NC-SA 4.0