简介

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

这种方式存在以下问题:

  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 在所有上下文中可以互换
#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)

弱序允许等价的值在所有方面不完全相同,但在排序时被视为相等。

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)。

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 来自动生成比较运算符:

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

注意事项

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

总结

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

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

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