提问人:xmllmx 提问时间:7/2/2021 最后编辑:cigienxmllmx 更新时间:7/5/2021 访问量:7555
当“运算符 <=>”就足够时,为什么我必须提供“运算符 ==”?
Why must I provide 'operator ==' when 'operator <=>' is enough?
问:
#include <compare>
struct A
{
int n;
auto operator<=>(A const& other) const
{
if (n < other.n)
{
return std::strong_ordering::less;
}
else if (n > other.n)
{
return std::strong_ordering::greater;
}
else
{
return std::strong_ordering::equal;
}
}
// compile error if the following code is commented out.
// bool operator==(A const& other) const
// { return n == other.n; }
};
int main()
{
A{} == A{};
}
观看在线演示
为什么我必须提供什么时候足够?operator ==
operator <=>
答:
因为有时实现得比使用 ,所以编译器默认拒绝使用潜在的次优实现。==
a <=> b == 0
例如,考虑 ,它可以在循环元素之前检查大小是否相同。std::string
请注意,您不必手动实现。你可以,这将在 .==
=default
<=>
另请注意,如果您自己,那么 ing 是没有必要的。=default
<=>
=default
==
评论
不,你没有。 只需添加
bool operator==(A const& other) const = default;
https://godbolt.org/z/v1WGhxca6
您始终可以将它们重载为不同的语义。为了防止意外的自动生成函数,需要= default
评论
operator<=>
为什么我必须提供什么时候足够?
operator==
operator<=>
嗯,主要是因为这还不够:-)
当 C++ 重写语句时,相等和排序是不同的桶:
平等 | 订购 | |
---|---|---|
主要 | == | <=> |
二 次 | != | <, >, <=, >= |
主算子具有反转的能力,而辅助算子具有根据其对应的主算子重写的能力:
- 倒车意味着可以是:
a == b
a.operator==(b)
如果有的话;或b.operator==(a)
如果没有。
- 重写意味着可以是:
a != b
! a.operator==(b)
如果可用
如果您必须重写和反转它,最后一个也可能是(我不完全确定,因为我的经验主要是比较相同的类型)。! b.operator==(a)
但是,默认情况下不跨相等/排序边界进行重写的要求意味着它不是 的重写候选者。<=>
==
等式和有序这样分开的原因可以在这篇 P1185 论文中找到,来自讨论这个问题的众多标准会议之一。
在许多情况下,自动实现可能非常低效。字符串、向量、数组或任何其他集合浮现在脑海中。您可能不想用于检查两个字符串的相等性:==
<=>
<=>
"xxxxx(a billion other x's)"
;和"xxxxx(a billion other x's)_and_a_bit_more"
.
这是因为必须处理整个字符串才能确定排序,然后检查排序是否强相等。<=>
但是,预先进行简单的长度检查会很快告诉您它们不相等。这是O(n)时间复杂度(十亿左右的比较)和O(1)之间的差异,O(1)是一个近乎即时的结果。
如果你知道它没问题(或者你乐于接受它可能带来的任何性能打击),你总是可以默认相等。但人们认为最好不要让编译器为你做出这个决定。
更详细地说,请考虑以下完整程序:
#include <iostream>
#include <compare>
class xyzzy {
public:
xyzzy(int data) : n(data) { }
auto operator<=>(xyzzy const &other) const {
// Could probably just use: 'return n <=> other.n;'
// but this is from the OPs actual code, so I didn't
// want to change it too much (formatting only).
if (n < other.n) return std::strong_ordering::less;
if (n > other.n) return std::strong_ordering::greater;
return std::strong_ordering::equal;
}
//auto operator==(xyzzy const &other) const {
// return n == other.n;
//}
//bool operator==(xyzzy const &) const = default;
private:
int n;
};
int main() {
xyzzy twisty(3);
xyzzy passages(3);
if (twisty < passages) std::cout << "less\n";
if (twisty == passages) std::cout << "equal\n";
}
它不会按原样编译,因为它需要 final 语句。但是你不必提供一个真正的(第一个注释掉的块),你可以告诉它使用默认值(第二个)。而且,在这种情况下,这可能是正确的决定,因为使用默认值不会对性能产生实际影响。operator==
请记住,仅当您显式提供三向比较运算符(当然,您使用 or )时,才需要提供相等运算符。如果两者都不提供,C++ 将同时提供这两个默认值。==
!=
而且,即使你必须提供两个函数(其中一个可能是默认的),它仍然比以前要好,你必须显式地提供它们,比如:
a == b
.a < b
.a != b
,定义为 。! (a == b)
a > b
,定义为 。! (a < b || a == b)
a <= b
,定义为 。a < b || a == b
a >= b
,定义为 。! (a < b)
评论
当“运算符 <=>”就足够时,为什么我必须提供“运算符 ==”?
因为它不会被使用。
如果您要使用默认的就足够了:
struct A
{
int n;
auto operator<=>(A const& other) const = default;
};
基本上,它可能比 并且在许多情况下这是一种选择。当您提供用户定义时,语言实现无法知道后者是否可以替换为前者,也无法知道后者是否不必要地效率低下。n == n
(a <=> a) == std::strong_ordering::equal
<=>
因此,如果您需要自定义三向比较,则需要自定义相等性比较。示例类不需要自定义三向比较,因此应使用默认比较。
评论
从前面的答案来看,没有人解决另一个问题:出于排序目的,两个对象可能是等价的,但并不相等。例如,我可能想在 Unicode NFC 规范化中对字符串进行排序,并将大小写折叠为小写,但对于相等性测试,我想验证字符串是否实际上相同,大小写很重要,甚至可能区分输入中的 é 和 ' + e。
是的,这是一个有点做作的例子,但它有助于说明,的定义不需要强排序,因此您甚至不能依赖潜在的返回。不能假定默认返回是有效的实现。<=>
<=>
std::strong_ordering::equal
==
<=>
std::strong_ordering::equal
评论
<=>
==
==
<=>