【问题标题】:No == operator found while comparing structs in C++比较 C++ 中的结构时找不到 == 运算符
【发布时间】:2011-08-10 01:33:09
【问题描述】:

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

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' 类型(或那里 是不可接受的转换)

为什么?

【问题讨论】:

    标签: c++ struct comparison-operators


    【解决方案1】:

    在 C++ 中,structs 默认没有生成比较运算符。你需要自己写:

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

    【讨论】:

    • @Jonathan:为什么 C++ 会知道你想如何比较你的 structs 是否相等?如果你想要简单的方法,只要你的结构不包含指针,总会有memcmp
    • @Xeo: memcmp 因非 POD 成员(如 std::string)和填充结构而失败。
    • @Jonathan 我所知道的“现代”语言确实提供了== 运算符——其语义几乎从来都不是我们想要的。 (而且它们不提供覆盖它的方法,因此您最终不得不使用成员函数)。我知道的“现代”语言也不提供值语义,所以你不得不使用指针,即使它们不合适。
    • @Jonathan Cases 肯定会有所不同,即使在给定的程序中也是如此。对于实体对象,Java 提供的解决方案效果很好(当然,您可以在 C++ 中做完全相同的事情——它甚至是实体对象的惯用 C++)。问题是如何处理值对象。出于 C 兼容性的原因,C++ 提供了默认的operator=(即使它经常做错事)。但是,C 兼容性不需要operator==。在全球范围内,我更喜欢 C++ 所做的而不是 Java 所做的。 (我不懂 C#,所以也许这样更好。)
    • 至少它应该可以= default它!
    【解决方案2】:

    C++20 引入了default comparisons, aka the "spaceship" operator<=>,它允许你请求编译器生成的</<=/==/!=/>=/ 和/或> 运算符/naive(?) 实现...

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

    ...但是您可以针对更复杂的情况进行自定义(如下所述)。请参阅 here 以获取包含理由和讨论的语言提案。这个答案仍然与 C++17 及更早版本相关,并且有助于深入了解何时应该自定义 operator&lt;=&gt;.... 的实现。

    C++ 之前没有标准化这似乎有点无益,但结构/类通常有一些数据成员要从比较中排除(例如计数器、缓存结果、容器容量、最后操作成功/错误代码、光标),以及要做出的决定关于无数事情,包括但不限于:

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

    所以,在您明确考虑比较对您的特定结构意味着什么之前,出现错误是一种很好的选择而不是让它编译但不给您一个有意义的运行时的结果

    说了这么多,如果 C++ 让你说 bool operator==() const = default; 当你决定一个“幼稚的”逐个成员 == 测试 没问题时,那就太好了。 != 也一样。鉴于多个成员/基础,“默认”&lt;&lt;=&gt;&gt;= 实现似乎毫无希望 - 根据声明顺序级联可能但不太可能是想要的,因为相互冲突的命令用于成员排序(基础必须在成员之前,按可访问性分组,在依赖使用之前构造/销毁)。为了更广泛地使用,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&amp; lhs, const MyStruct2&amp; rhs) 函数将支持...

    my_MyStruct2 == my_MyStruct1
    

    ...首先从my_myStruct2 创建一个临时的MyStruct1,然后进行比较。这肯定会将MyStruct1::an_int 设置为构造函数的默认参数值-1。根据您是否在 operator== 的实现中包含 an_int 比较,MyStruct1 可能与 MyStruct2 比较,而 MyStruct2 本身比较等于 MyStruct1my_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 痛苦地创造出来只是为了被扔掉!对于具有浮点值的类型,比较可见表示意味着显示的位数决定了在比较期间几乎相等的值被视为相等的容差。

    【讨论】:

    • 好吧,实际上对于比较运算符 , = 它应该只需要实现
    • @André:更常见的是手动编写的 int cmp(x, y)compare 函数返回负值 x &lt; y,0 表示相等,正值 x &gt; y 用作基础&lt;&gt;&lt;=&gt;===!=;使用 CRTP 将所有这些运算符注入到一个类中非常容易。我确定我已经在旧答案中发布了实现,但无法快速找到它。
    • @TonyD 当然你可以这样做,但是就&lt; 而言,实现&gt;&lt;=&gt;= 一样容易。您也可以以这种方式实现==!=,但我猜这通常不是一个非常有效的实现。如果所有这些都不需要 CRTP 或其他技巧,那就太好了,但是如果用户没有明确定义并且定义了 &lt;,标准只会强制自动生成这些运算符。
    • @André:这是因为==!= 可能无法使用&lt; 有效地表达,所以对所有内容使用比较是很常见的。 “如果不需要 CRTP 或其他技巧就好了” - 也许,但是 CRTP 可以很容易地用于生成许多其他运算符(例如按位|&amp;、@ 987654412@ 来自 |=&amp;=^=+ - * / % 来自它们的赋值形式;二进制 - 来自一元化 4422@gation)这个主题的潜在有用变体,只是为其中一个相当随意的部分提供语言功能并不是特别优雅。
    • 您介意在一个合理的实现中添加一个使用std::tie 来比较多个成员的版本吗?
    【解决方案3】:

    您需要为MyStruct1 显式定义operator ==

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

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

    【讨论】:

      【解决方案4】:

      从 C++20 开始,应该可以通过声明 default three-way comparison operator(“宇宙飞船”运算符)将一整套默认比较运算符(==&lt;= 等)添加到类中,像这样:

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

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

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          比较不适用于 C 或 C++ 中的结构。而是按字段进行比较。

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              因为您没有为结构编写比较运算符。编译器不会为你生成,所以如果你想比较,你必须自己写。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2023-04-05
                • 2013-04-28
                • 1970-01-01
                • 2019-11-26
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-03-03
                相关资源
                最近更新 更多