在C ++中比较结构时找不到==运算符


96

比较以下结构的两个实例,我收到一个错误:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

错误是:

错误C2678:二进制'==':未找到采用类型为'myproj :: MyStruct1'的左侧操作数的运算符(或没有可接受的转换)

为什么?

Answers:


126

在C ++中,struct默认情况下不会生成s比较运算符。您需要编写自己的:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}

21
@Jonathan:为什么C ++知道您想如何比较structs的相等性?而且,如果您想要简单的方法,memcmp那么您的结构总是不包含指针那么长时间。
Xeo

12
@Xeo:memcmp使用非POD成员(如std::string)和填充结构失败。
fredoverflow 2011年

16
@Jonathan我所知道的“现代”语言确实为==运算符提供了一种几乎不需要的语义。(而且它们没有提供覆盖它的方法,因此您最终不得不使用成员函数)。我知道的“现代”语言也不提供值语义,因此即使它们不合适,您也不得不使用指针。
James Kanze 2011年

4
@Jonathan Cases确实确实有所不同,即使在给定程序中也是如此。对于实体对象,Java提供的解决方案效果很好(当然,您可以在C ++中做完全一样的事情-甚至对于实体对象来说都是惯用的C ++)。问题是如何处理价值对象。operator=出于C兼容性的原因,C ++提供了默认值(即使它经常做错事)。C兼容性不需要operator==。在全球范围内,我更喜欢C ++而不是Java。(我不懂C#,所以也许更好。)
James Kanze 2011年

9
至少应该有可能= default
user362515 '16

94

C ++ 20引入了默认的比较,也就是“ spaceship”operator<=>,它使您可以请求编译器生成的<// <=/ ==/ !=/ >=/和/或>具有明显/天真的(?)实现的运算符...

auto operator<=>(const MyClass&) const = default;

...但是您可以针对更复杂的情况(在下面讨论)进行自定义。有关语言建议,请参见此处,其中包含论据和讨论。这个答案仍然是相关的C ++ 17和更早的版本,以及洞察力的时候,你应该定制的实施operator<=>....

对于C ++而言,如果没有早先对此进行标准化,可能会有所帮助,但是结构/类通常具有一些要从比较中排除的数据成员(例如,计数器,缓存的结果,容器容量,上次操作成功/错误代码,游标),例如以及决定作出关于万物,包括但不限于:

  • 首先比较哪个字段,例如比较特定int成员可能会很快消除99%的不相等对象,而一个map<string,string>成员可能经常具有相同的条目并且比较起来比较昂贵-如果在运行时加载值,程序员可能会发现编译器不可能
  • 比较字符串时:区分大小写,空格和分隔符的等效性,转义约定...
  • 比较浮点/双精度时的精度
  • NaN浮点值是否应视为相等
  • 比较指针或指向数据(如果是后者,则如何知道指针是否指向数组以及需要比较多少对象/字节)
  • 是否为了事项比较无序的容器(如vectorlist),如果是是否这是确定比较与每一个比较完成的时间使用额外的内存来排序的临时前将其就地排序
  • 当前有多少个数组元素持有应比较的有效值(某处是否有大小或前哨?)
  • union比较哪个成员
  • 归一化:例如,日期类型可能允许超出范围的某月某天或某年的某个月,或者有理/分数对象可能有6/8的精度,而另一个有3/4的精度,出于性能原因,它们可以纠正懒惰地进行单独的标准化步骤;您可能需要在比较之前决定是否触发标准化
  • 弱指针无效时该怎么办
  • 如何处理未实现operator==自身的成员和基础(但可能具有compare()or operator<str()or getters ...)
  • 在读取/比较其他线程可能想要更新的数据时必须采取什么锁

因此,在您明确考虑比较对您的特定结构意味着什么之前,有一个错误会很好而不是让它编译但在运行时没有给您有意义的结果

综上所述,如果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...

当您“拥有”(即可以使用Corporate和3rd party库编辑一个因子)要比较的类时,尤其是C ++ 14准备从return语句中推断函数返回类型时,添加一个“将成员函数绑定到您想要比较的类:

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==()...

独立实施

您有一个有趣的决定。由于您的类可以由隐式构造MyStruct2,因此独立/非成员bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)函数将支持...

my_MyStruct2 == my_MyStruct1

...首先创建一个临时MyStruct1my_myStruct2,然后做比较。这肯定会保留MyStruct1::an_int为构造函数的默认参数值-1。取决于您是否在您an_int的实现中包含比较operator==,一个MyStruct1可能等于或可能不等于MyStruct2本身本身等于MyStruct1my_struct_2成员!此外,创建临时项MyStruct1可能是非常低效的操作,因为它涉及将现有my_struct2成员复制到临时项,而只是在比较之后将其丢弃。(当然,您可以MyStruct1通过构造explicit或删除的默认值来防止s的这种隐式构造以进行比较an_int)。

会员实施

如果要避免MyStruct1从隐式构造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痛苦地创造出来的东西被扔掉了!对于具有浮点值的类型,比较可见表示表示显示位数的数量确定了比较期间将近似等于的值视为相等的容差。


好吧,实际上对于比较运算符<,>,<=,> =,仅应实现<。其余部分将随之而来,并且没有实现它们的有意义的方法,这意味着与可以自动生成的实现不同的任何东西。您必须自己实施它们,这很奇怪。
安德烈

@安德烈:更频繁地手动书面int cmp(x, y)compare函数返回一个负值x < y,平等和0的正值x > y被用作基础<><=>===,和!=; 使用CRTP将所有这些运算符注入到一个类中非常容易。我确定我已经在旧的答案中发布了实现,但是找不到很快。
托尼·德罗伊

@TonyD当然,你可以做到这一点,但它也很容易实现><=>=在以下方面<。您可能还实现了==!=这种方式,但通常不会是一个非常有效的实现我猜。如果不需要所有这些都需要CRTP或其他技巧,那将是很好的选择,但是如果用户未明确定义并<定义该标准,则该标准仅会强制要求自动生成这些运算符。
安德烈(André)

@André:这是因为==并且!=可能无法<使用对所有内容进行比较的方式来有效地表达它。 “这将是很好,如果没有CRTP或其他的技巧就需要” -也许,但随后CRTP可以很容易地被用来产生大量的其他经营者(如按位|&^|=&=并且^=+ - * / %从他们的分配形式;二-,从一元否定和+)-关于此主题的许多潜在有用的变化,以至仅为其中的任意一部分提供语言功能就不是特别优雅。
托尼·德罗伊

您介意将一个用于比较多个成员的版本添加到一个可行的实现中std::tie吗?
NathanOliver

17

您需要明确定义operator ==MyStruct1

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

现在==比较对于2个这样的对象是合法的。


11

在C ++ 20日起,它应该是可能的全套默认比较操作(添加==<=通过声明等)一类的默认三路比较操作符(“宇宙飞船”运营商),就像这样:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

使用兼容的C ++ 20编译器,假定MyStruct2的定义兼容,则将该行添加到MyStruct1和MyStruct2可能足以进行相等比较。



2

默认情况下,结构没有==运算符。您必须编写自己的实现:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }

0

开箱即用的==运算符仅适用于基元。为了使代码正常工作,您需要为结构重载==运算符。


0

因为您没有为结构编写比较运算符。编译器不会为您生成它,因此,如果要比较,则必须自己编写。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.