C++20 新特性: 三向比较运算符
目录
C++20 引入了一种新的比较运算符,称为"三向比较运算符"或"太空船运算符",其符号为 <=>
。这个运算符提供了一种简化方式来同时比较两个值的相等性、小于和大于状态。这一特性主要旨在简化代码并改善性能,通过一次操作就能得到完整的比较结果。
功能和用法
三向比较运算符返回一个名为 std::strong_ordering
、std::weak_ordering
或 std::partial_ordering
的特殊类型,这些类型都是从 std::compare_three_way
派生的。它们可以表达小于、等于、大于三种状态:
std::strong_ordering::less
:表示左侧值小于右侧值。std::strong_ordering::equal
:表示两个值相等。std::strong_ordering::greater
:表示左侧值大于右侧值。
示例代码
考虑以下结构体 Point
,使用三向比较运算符来比较两个点:
cpp
#include <compare>
#include <iostream>
struct Point {
int x, y;
auto operator<=>(const Point& other) const = default; // 使用默认生成的比较
};
int main() {
Point p1{1, 2};
Point p2{1, 3};
auto result = p1 <=> p2;
if (result == std::strong_ordering::less) {
std::cout << "p1 is less than p2";
} else if (result == std::strong_ordering::equal) {
std::cout << "p1 is equal to p2";
} else if (result == std::strong_ordering::greater) {
std::cout << "p1 is greater than p2";
}
}
以上代码中,Point
结构体包含两个成员变量:x
和 y
,都是整数类型。结构体重载了三向比较运算符 <=>
,使用默认方式生成,这意味着比较会按成员顺序进行:首先比较 x
,如果 x
相等,则比较 y
。
这里的三向比较运算符 <=>
返回一个 std::strong_ordering
类型的结果,表示两个对象的相对大小关系。
p1
初始化为{1, 2}
(即x=1
,y=2
)。p2
初始化为{1, 3}
(即x=1
,y=3
)。
当使用 p1 <=> p2
进行比较时,首先比较它们的 x
值。由于 p1.x
和 p2.x
都是 1,所以相等,比较进入下一步,比较 y
值。此时,p1.y
是 2,而 p2.y
是 3,所以 p1
小于 p2
,因此 result
的值为 std::strong_ordering::less
。
在我们的 main
函数中,根据 result
的值,输出相应的信息。因此,最终输出将是 "p1 is less than p2",表示点 p1
在按 x 后 y 的顺序比较时小于点 p2
。
当然,三向比较运算符 <=>
在 C++20 中也可以用于运算符重载。这使得开发者能够为自定义类型定义一种自然的比较逻辑,通过一次比较即可判断出小于、等于或大于的关系。这种能力特别适用于那些需要经常进行排序或比较的数据结构。
如何重载三向比较运算符
当你重载 <=>
运算符时,你可以选择返回 std::strong_ordering
、std::weak_ordering
或 std::partial_ordering
,这取决于你的类型是否总是可以完全排序。
std::strong_ordering
:适用于那些总是可以完全比较的类型。std::weak_ordering
:适用于比较可能因等价元素的不同而不完全的类型。std::partial_ordering
:适用于可能不具有全序性的类型(如浮点数)。
示例:重载三向比较运算符
这里有一个简单的示例,展示如何为一个包含整数成员的类重载 <=>
运算符:
cpp
#include <iostream>
#include <compare>
class Widget {
public:
int value;
int priority;
Widget(int v, int p) : value(v), priority(p) {}
// 自定义三向比较运算符的逻辑
auto operator<=>(const Widget& other) const {
if (auto p = priority <=> other.priority; p != 0) {
return p; // 如果优先级不同,根据优先级比较结果返回
}
return value <=> other.value; // 如果优先级相同,根据值比较结果返回
}
};
int main() {
Widget w1(10, 2), w2(20, 1);
auto result = w1 <=> w2;
if (result < 0) {
std::cout << "w1 is less important or has a lower value than w2";
} else if (result == 0) {
std::cout << "w1 is equal to w2";
} else if (result > 0) {
std::cout << "w1 is more important or has a higher value than w2";
}
}
在这个示例中,我们使用 <=>
来定义 Widget
类的比较逻辑。此逻辑首先比较对象的 priority
成员。当使用 <=>
运算符比较两个 int
类型的 priority
成员时,结果的类型被自动推导为 std::strong_ordering
,因为整型比较总是提供一个全序比较结果。
如果 priority
的比较结果不为零(即它们不相等),则直接返回该结果,表示一个对象比另一个对象"更大"或"更小"。
只有当两个对象的 priority
相等时,我们才会继续比较 value
成员。此处同样使用 <=>
运算符比较 int
类型的 value
,结果也自动推导为 std::strong_ordering
。我们不需要 为每一种比较类型定义重载版本的比较运算符。只需要定义一个 <=>
运算符,并根据我们成员变量的类型和比较逻辑选择合适的返回类型。例如,如果类包含浮点数,auto会选择返回 std::partial_ordering
。
这种方式确保了比较操作符的返回类型一致性,并且符合我们特定的业务逻辑,即在 priority
相同的情况下,通过 value
的大小进一步区分对象的排序。
整体上,这种比较方法使得 Widget
类的实例比较操作不仅简洁高效,还能确保对象间的比较遵循我们设定的"优先级首先,然后是值"的逻辑,从而满足特定的业务需求,如视优先级较高的对象为"更重要"或"更优先"。
注意事项
- 隐式重载
==
运算符 :在 C++20 中,如果你为类重载了<=>
运算符,并且类中所有参与比较的成员也重载了==
运算符,那么==
运算符将被隐式地重载。如果需要不同的比较逻辑,应显式重载==
运算符。 - 支持全序关系 :使用
<=>
运算符时,确保参与比较的数据类型支持全序关系。对于可能不具有全序特性的类型(如浮点数),应使用std::partial_ordering
。 - 复杂类成员的手动实现 :对于包含复杂成员变量的类,可能需要手动实现
<=>
运算符来确保正确的行为,尤其是在类的成员变量比较复杂或者不直接支持<=>
运算符时。
这些注意事项强调了在实现和使用三向比较运算符时需要考虑的关键点,确保类型比较操作符的正确使用和实现。