C++20 引入了default comparisons, aka the "spaceship" operator<=>,它允许你请求编译器生成的</<=/==/!=/>=/ 和/或> 运算符/naive(?) 实现...
auto operator<=>(const MyClass&) const = default;
...但是您可以针对更复杂的情况进行自定义(如下所述)。请参阅 here 以获取包含理由和讨论的语言提案。这个答案仍然与 C++17 及更早版本相关,并且有助于深入了解何时应该自定义 operator<=>.... 的实现。
C++ 之前没有标准化这似乎有点无益,但结构/类通常有一些数据成员要从比较中排除(例如计数器、缓存结果、容器容量、最后操作成功/错误代码、光标),以及要做出的决定关于无数事情,包括但不限于:
- 首先比较哪些字段,例如比较特定的
int 成员可能会很快消除 99% 的不相等对象,而 map<string,string> 成员可能通常具有相同的条目并且比较成本相对较高 - 如果在运行时加载值,程序员可能会了解编译器不可能
- 比较字符串:区分大小写、空格和分隔符的等效性、转义约定...
- 比较浮点数/双精度数时的精度
- 是否应将 NaN 浮点值视为相等
- 比较指针或指向数据(如果是后者,如何知道指针是否指向数组以及需要比较的对象/字节数)
- 比较未排序的容器时顺序是否重要(例如
vector、list),如果是,是否可以在比较之前对它们进行就地排序,还是在每次比较完成时使用额外的内存对临时对象进行排序
- 当前有多少数组元素包含应比较的有效值(是否有大小或标记?)
- 要比较
union 的哪个成员
- 规范化:例如,日期类型可能允许超出范围的日期或年份,或者一个有理/分数对象可能有 6/8 而另一个有 3/4,这是为了性能他们通过单独的标准化步骤懒惰地纠正的原因;您可能必须在比较之前决定是否触发归一化
- 当弱指针无效时怎么办
- 如何处理自己没有实现
operator== 的成员和基(但可能有compare() 或operator< 或str() 或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;
}
请注意,MyStruct2 也需要 operator==。
此实现的含义和替代方案将在下面的标题讨论您的 MyStruct1 的细节下进行讨论。
==、
利用std::tuple 的比较运算符来比较您自己的类实例很容易——只需使用std::tie 按所需的比较顺序创建对字段的引用元组。从here概括我的例子:
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...
当您“拥有”(即可以编辑具有公司库和第 3 方库的因素)您想要比较的类时,尤其是 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 operators(搜索less_than_comparable)。如果由于某种原因它不适合,您可能会也可能不喜欢支持宏的想法(online):
#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)
...然后可以使用 la...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(C++14 成员绑定版本here)
讨论你的 MyStruct1 的细节
选择提供独立与成员 operator==()...
独立实施
你要做出一个有趣的决定。由于您的类可以从MyStruct2 隐式构造,因此独立/非成员bool operator==(const MyStruct2& lhs, const MyStruct2& rhs) 函数将支持...
my_MyStruct2 == my_MyStruct1
...首先从my_myStruct2 创建一个临时的MyStruct1,然后进行比较。这肯定会将MyStruct1::an_int 设置为构造函数的默认参数值-1。根据您是否在 operator== 的实现中包含 an_int 比较,MyStruct1 可能与 MyStruct2 比较,而 MyStruct2 本身比较等于 MyStruct1 的 my_struct_2 成员!此外,创建一个临时的MyStruct1 可能是一个非常低效的操作,因为它涉及将现有的my_struct2 成员复制到一个临时成员,只是在比较后将其丢弃。 (当然,您可以通过将构造函数设置为explicit 或删除an_int 的默认值来防止这种隐式构造MyStruct1s 进行比较。)
成员实施
如果您想避免从 MyStruct2 隐式构造 MyStruct1,请将比较运算符设为成员函数:
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();
...这通常也很昂贵——那些strings 痛苦地创造出来只是为了被扔掉!对于具有浮点值的类型,比较可见表示意味着显示的位数决定了在比较期间几乎相等的值被视为相等的容差。