C++ 20三路比较
引言
在C++20之前,如果我们想要比较两个对象的大小关系,通常需要实现六种比较运算符:==
、!=
、<
、<=
、>
、>=
。这不仅工作量大,而且容易出错,特别是在确保各个运算符行为一致性方面。C++20引入了三路比较运算符(也称为"太空船运算符")<=>
,它可以帮助我们大幅简化比较操作的实现,并确保比较行为的一致性。
什么是三路比较运算符?
三路比较运算符 <=>
的名称来源于其外观,它看起来像一艘太空船,因此也被称为"太空船运算符"(spaceship operator)。
与传统的比较运算符不同,三路比较运算符不仅能判断两个值是否相等,还能判断它们的大小关系。它返回三种可能的结果:
- 小于零 - 表示左操作数小于右操作数
- 等于零 - 表示左操作数等于右操作数
- 大于零 - 表示左操作数大于右操作数
三路比较运算符的返回类型是一个比较类别类型,主要有:
std::strong_ordering
std::weak_ordering
std::partial_ordering
基本用法
下面是一个简单的例子,展示三路比较运算符的基本用法:
#include <iostream>
#include <compare>
int main() {
int a = 5;
int b = 10;
auto result = a <=> b;
if (result < 0) {
std::cout << "a < b" << std::endl;
} else if (result == 0) {
std::cout << "a == b" << std::endl;
} else { // result > 0
std::cout << "a > b" << std::endl;
}
return 0;
}
输出:
a < b
比较类别类型
C++20引入了三种比较类别类型,用于表示不同类型的比较结果:
1. std::strong_ordering
表示全序比较,包含三个值:less
、equal
和greater
。这是最强的比较类型,适用于整数、枚举等类型。
std::strong_ordering::less // <
std::strong_ordering::equal // ==
std::strong_ordering::greater // >
2. std::weak_ordering
表示弱序比较,也包含三个值:less
、equivalent
和greater
。适用于区分相等性(equality)和等价性(equivalence)的情况,如不区分大小写的字符串比较。
std::weak_ordering::less // <
std::weak_ordering::equivalent // ==
std::weak_ordering::greater // >
3. std::partial_ordering
表示偏序比较,增加了一个额外的值:unordered
。适用于存在不可比较值的类型,如浮点数(考虑NaN的情况)。
std::partial_ordering::less // <
std::partial_ordering::equivalent // ==
std::partial_ordering::greater // >
std::partial_ordering::unordered // 无法比较
默认比较
C++20还引入了默认比较功能,可以通过添加= default
来自动生成比较运算符:
#include <iostream>
#include <compare>
struct Point {
int x;
int y;
// 自动生成三路比较运算符
auto operator<=>(const Point&) const = default;
};
int main() {
Point p1{1, 2};
Point p2{1, 3};
if (p1 < p2) {
std::cout << "p1 < p2" << std::endl;
} else {
std::cout << "p1 >= p2" << std::endl;
}
return 0;
}
输出:
p1 < p2
默认生成的三路比较运算符会按声明顺序逐个比较类的成员。在上面的例子中,首先比较x
,如果相等,再比较y
。
自定义比较方式
除了使用默认比较外,我们也可以自定义比较逻辑:
#include <iostream>
#include <compare>
#include <string>
class Person {
private:
std::string name;
int age;
public:
Person(std::string n, int a) : name(std::move(n)), age(a) {}
// 自定义比较:按年龄比较
std::strong_ordering operator<=>(const Person& other) const {
return age <=> other.age;
}
// 还需要自定义相等比较
bool operator==(const Person& other) const {
return age == other.age;
}
const std::string& getName() const { return name; }
int getAge() const { return age; }
};
int main() {
Person p1{"Alice", 30};
Person p2{"Bob", 25};
if (p1 > p2) {
std::cout << p1.getName() << " is older than " << p2.getName() << std::endl;
} else if (p1 < p2) {
std::cout << p1.getName() << " is younger than " << p2.getName() << std::endl;
} else {
std::cout << p1.getName() << " is the same age as " << p2.getName() << std::endl;
}
return 0;
}
输出:
Alice is older than Bob
请注意,当自定义<=>
运算符时,C++20不会自动生成==
运算符。如果需要相等比较功能,你仍然需要自己实现==
运算符。
三路比较与其他比较运算符的关系
在C++20中,当你定义了三路比较运算符<=>
和相等运算符==
后,C++编译器会自动为你生成其他四个比较运算符(!=
、<
、<=
、>
、>=
)。这大大减少了编写代码的工作量,并确保了各个运算符之间的一致性。
下面的代码展示了此功能的工作原理:
#include <iostream>
#include <compare>
struct Value {
int value;
// 只需定义这两个运算符
auto operator<=>(const Value&) const = default;
bool operator==(const Value&) const = default;
// 编译器会自动生成:
// bool operator!=(const Value&) const
// bool operator<(const Value&) const
// bool operator<=(const Value&) const
// bool operator>(const Value&) const
// bool operator>=(const Value&) const
};
int main() {
Value v1{10};
Value v2{20};
if (v1 != v2) std::cout << "v1 != v2" << std::endl;
if (v1 < v2) std::cout << "v1 < v2" << std::endl;
if (v1 <= v2) std::cout << "v1 <= v2" << std::endl;
if (v2 > v1) std::cout << "v2 > v1" << std::endl;
if (v2 >= v1) std::cout << "v2 >= v1" << std::endl;
return 0;
}
输出:
v1 != v2
v1 < v2
v1 <= v2
v2 > v1
v2 >= v1
实际应用场景
1. 自定义类的排序
三路比较运算符特别适合实现自定义类的排序功能:
#include <iostream>
#include <vector>
#include <algorithm>
#include <compare>
struct Student {
std::string name;
int score;
// 首先按分数降序排序,分数相同时按姓名升序排序
std::strong_ordering operator<=>(const Student& other) const {
if (auto cmp = other.score <=> score; cmp != 0)
return cmp;
return name <=> other.name;
}
bool operator==(const Student& other) const = default;
};
int main() {
std::vector<Student> students = {
{"Alice", 85},
{"Bob", 90},
{"Charlie", 85},
{"David", 95}
};
std::sort(students.begin(), students.end());
for (const auto& student : students) {
std::cout << student.name << ": " << student.score << std::endl;
}
return 0;
}
输出:
David: 95
Bob: 90
Alice: 85
Charlie: 85
2. 实现版本号比较
三路比较运算符可以轻松实现版本号的比较逻辑:
#include <iostream>
#include <compare>
#include <vector>
class Version {
private:
std::vector<int> segments;
public:
Version(std::initializer_list<int> segs) : segments(segs) {}
std::strong_ordering operator<=>(const Version& other) const {
// 按位比较版本号
for (size_t i = 0; i < std::min(segments.size(), other.segments.size()); ++i) {
if (auto cmp = segments[i] <=> other.segments[i]; cmp != 0) {
return cmp;
}
}
// 处理不同长度的版本号
return segments.size() <=> other.segments.size();
}
bool operator==(const Version& other) const = default;
// 显示版本号
friend std::ostream& operator<<(std::ostream& os, const Version& v) {
for (size_t i = 0; i < v.segments.size(); ++i) {
if (i > 0) os << ".";
os << v.segments[i];
}
return os;
}
};
int main() {
Version v1{1, 2, 3};
Version v2{1, 3, 0};
Version v3{1, 2, 3, 0};
std::cout << v1 << " < " << v2 << ": " << (v1 < v2 ? "true" : "false") << std::endl;
std::cout << v1 << " < " << v3 << ": " << (v1 < v3 ? "true" : "false") << std::endl;
std::cout << v2 << " < " << v3 << ": " << (v2 < v3 ? "true" : "false") << std::endl;
return 0;
}
输出:
1.2.3 < 1.3.0: true
1.2.3 < 1.2.3.0: true
1.3.0 < 1.2.3.0: false
浮点数比较与 NaN 处理
使用三路比较运算符比较浮点数时,需要特别注意 NaN(Not a Number)的处理:
#include <iostream>
#include <compare>
#include <cmath>
int main() {
double a = 1.0;
double b = 2.0;
double nan = std::numeric_limits<double>::quiet_NaN();
auto result1 = a <=> b;
std::cout << "1.0 <=> 2.0: "
<< (result1 < 0 ? "less" : result1 == 0 ? "equal" : "greater")
<< std::endl;
auto result2 = a <=> nan;
std::cout << "1.0 <=> NaN: ";
if (result2 < 0) std::cout << "less";
else if (result2 == 0) std::cout << "equal";
else if (result2 > 0) std::cout << "greater";
else std::cout << "unordered";
std::cout << std::endl;
// 检测是否可比较
if (std::isunordered(a, nan)) {
std::cout << "a and nan are unordered" << std::endl;
}
return 0;
}
输出:
1.0 <=> 2.0: less
1.0 <=> NaN: unordered
a and nan are unordered
当涉及 NaN 的比较时,结果通常是"无序的"(unordered)。在处理可能包含 NaN 的浮点数据时,务必要小心。
总结
C++20 的三路比较运算符(<=>)
是一项强大的语言特性,它可以:
- 简化比较运算符的实现
- 确保比较操作的一致性
- 通过默认实现轻松处理结构体和类的比较
- 提供更细粒度的比较结果类型
使用三路比较运算符,可以显著减少编写比较运算符所需的代码量,并降低出错的可能性。特别是在处理复杂数据结构的排序和比较时,三路比较运算符能够带来更简洁、更可靠的代码。
练习
- 创建一个
Rectangle
类,包含宽度和高度,并使用三路比较运算符实现按面积比较。 - 实现一个
IPAddress
类,使用三路比较运算符比较 IPv4 地址。 - 为一个含有多个成员的结构体实现自定义比较逻辑,使用三路比较运算符。
- 尝试在代码中使用
std::partial_ordering
处理包含 NaN 的浮点数比较。
扩展阅读
- C++20 标准文档中关于三路比较运算符的部分
- 关于比较类别类型的更多细节,可以阅读
<compare>
头文件的文档 - 探索 C++20 中与三路比较运算符协同工作的其他新特性,如默认函数模板参数
通过深入理解和使用三路比较运算符,你可以编写出更简洁、更健壮的比较代码,这是 C++20 为现代 C++ 编程带来的重要改进之一。