C++20 引入了默认比较,也就是“宇宙飞船”operator<=>
,它允许您请求编译器生成的/////和/或<
具有明显<=
/ naive(?) 实现的运算符...==
!=
>=
>
auto operator<=>(const MyClass&) const = default;
...但是您可以针对更复杂的情况进行自定义(如下所述)。有关语言建议,请参见此处,其中包含理由和讨论。这个答案仍然与 C++17 及更早版本相关,并且有助于深入了解何时应该自定义operator<=>
....
C++ 之前没有标准化这似乎有点无益,但通常结构/类有一些数据成员要从比较中排除(例如计数器、缓存结果、容器容量、最后操作成功/错误代码、游标),如以及对无数事情做出的决定,包括但不限于:
- 首先比较哪些字段,例如比较特定
int
成员可能会很快消除 99% 的不相等对象,而map<string,string>
成员可能通常具有相同的条目并且比较成本相对较高 - 如果在运行时加载值,程序员可能会了解编译器不可能
- 在比较字符串时:区分大小写、空格和分隔符的等价性、转义约定......
- 比较浮点数/双精度数时的精度
- 是否应将 NaN 浮点值视为相等
- 比较指针或指向数据(如果是后者,如何知道指针是否指向数组以及需要比较的对象/字节数)
- 在比较未排序的容器(例如
vector
, list
)时顺序是否重要,如果是,是否可以在比较之前对它们进行就地排序与每次比较完成时使用额外的内存对临时对象进行排序
- 当前有多少个数组元素包含应该比较的有效值(某处是否有大小或哨兵?)
union
比较a 的哪个成员
- 规范化:例如,日期类型可能允许超出范围的日期或月份,或者一个有理/分数对象可能有 6/8 而另一个有 3/4,出于性能原因,它们会更正懒惰地使用单独的标准化步骤;您可能必须在比较之前决定是否触发标准化
- 当弱指针无效时该怎么办
- 如何处理
operator==
自己没有实现的成员和基础(但可能有compare()
or operator<
or or str()
or getter...)
- 在读取/比较其他线程可能想要更新的数据时必须使用哪些锁
因此,在您明确考虑比较对您的特定结构意味着什么之前,最好有一个错误,而不是让它编译但在运行时没有给您一个有意义的结果。
综上所述,如果 C++ 让您说您bool operator==() const = default;
何时决定“幼稚”的逐个成员==
测试是可以的,那就太好了。对!=
. 鉴于多个成员/基数,“默认” 、、、<
和实现似乎无望——根据声明顺序级联可能但不太可能是想要的,因为成员排序的必要性相互冲突(基数必须在成员之前,按可访问性,依赖使用前的构造/破坏)。为了更广泛地使用,C++ 需要一个新的数据成员/基础注释系统来指导选择——尽管这在标准中是一件很棒的事情,理想情况下与基于 AST 的用户定义代码生成相结合......我希望它'<=
>
>=
等式运算符的典型实现
一个合理的实现
一个合理和有效的实施可能是:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.my_struct2 == rhs.my_struct2 &&
lhs.an_int == rhs.an_int;
}
请注意,这也需要一个operator==
for MyStruct2
。
此实现的含义和替代方案将在下面的“MyStruct1 的细节讨论”标题下进行讨论。
==、<、> <= 等的一致方法
std::tuple
利用的比较运算符来比较您自己的类实例很容易- 只需使用std::tie
以所需的比较顺序创建对字段的引用元组。从这里概括我的例子:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) ==
std::tie(rhs.my_struct2, rhs.an_int);
}
inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) <
std::tie(rhs.my_struct2, rhs.an_int);
}
// ...etc...
当您“拥有”(即可以编辑具有公司和第三方库的因素)您想要比较的类时,尤其是在 C++14 准备从return
语句中推断函数返回类型的情况下,添加“ tie" 成员函数与您希望能够比较的类:
auto tie() const { return std::tie(my_struct1, an_int); }
然后上面的比较简化为:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.tie() == rhs.tie();
}
如果您想要更完整的比较运算符集,我建议使用boost 运算符(搜索less_than_comparable
)。如果由于某种原因它不适合,您可能会也可能不喜欢支持宏(在线)的想法:
#define TIED_OP(STRUCT, OP, GET_FIELDS) \
inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
{ \
return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
}
#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
TIED_OP(STRUCT, ==, GET_FIELDS) \
TIED_OP(STRUCT, !=, GET_FIELDS) \
TIED_OP(STRUCT, <, GET_FIELDS) \
TIED_OP(STRUCT, <=, GET_FIELDS) \
TIED_OP(STRUCT, >=, GET_FIELDS) \
TIED_OP(STRUCT, >, GET_FIELDS)
...然后可以使用...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(此处为 C++14 成员绑定版本)
讨论您的 MyStruct1 的细节
选择提供独立与会员的选择是有影响的operator==()
……
独立实施
你有一个有趣的决定要做。由于您的类可以从 a 隐式构造MyStruct2
,因此独立/非成员bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)
函数将支持...
my_MyStruct2 == my_MyStruct1
...首先创建一个临时MyStruct1
from my_myStruct2
,然后进行比较。这肯定会MyStruct1::an_int
设置为构造函数的默认参数值-1
。根据您是否an_int
在您的实现中包含比较operator==
,aMyStruct1
可能会或可能不会比较等于 aMyStruct2
本身比较等于MyStruct1
'smy_struct_2
成员!此外,创建临时MyStruct1
可能是一个非常低效的操作,因为它涉及将现有my_struct2
成员复制到临时,只是在比较后将其丢弃。(当然,您可以MyStruct1
通过创建构造函数explicit
或删除 s 的默认值来防止这种隐式构造 s 进行比较an_int
。)
会员实施
如果要避免MyStruct1
从 a 隐式构造 a MyStruct2
,请将比较运算符设为成员函数:
struct MyStruct1
{
...
bool operator==(const MyStruct1& rhs) const
{
return tie() == rhs.tie(); // or another approach as above
}
};
注意const
关键字 - 只需要成员实现 - 建议编译器比较对象不会修改它们,因此可以在const
对象上允许。
比较可见表示
有时,获得所需比较的最简单方法可能是......
return lhs.to_string() == rhs.to_string();
......这通常也非常昂贵 - 那些string
痛苦地创造出来只是为了被扔掉!对于具有浮点值的类型,比较可见表示意味着显示的位数决定了在比较期间几乎相等的值被视为相等的容差。