【问题标题】:C++ equal(==) overload, Shortcut or best way comparing all attributesC++ equal(==) 重载、快捷方式或比较所有属性的最佳方法
【发布时间】:2016-06-12 18:52:57
【问题描述】:

我必须在 C++ 中为具有许多属性的类重载 == 运算符。
当且仅当所有属性都相等时,运算符应该返回 true。如果这些属性随时间发生变化,则可以使用快捷方式来避免错误。

有比较一个类中每个属性的快捷方式吗?

【问题讨论】:

  • 如果您的对象是 POD 或它的大部分 POD(在那部分),您可以使用 memcmp
  • 您可以编写一个脚本(在您的编辑器中,如果它支持的话。例如,Vim 正则表达式替换可以这样做)获取声明行的副本并将它们转换为element == other.element &&跨度>
  • @user3545806 memcmp 不会考虑填充,所以这不起作用。
  • @Barry,如果你先 memset POD 部分然后使用 memcmp,它会起作用吗?但显然你是对的,我的评论缺少那个注释。
  • @VladimirS(和@Barry)关于memcmp - 我认为这比你在这里说的更糟糕。即使使用 POD,即使 POD 已预先初始化(例如,归零),用户也可能会在带有可区分联合的极端情况下被烧毁。假设用户有一个带有一个 char 和一个 int(假设 8 位和 32 位)的 union,以及一个 union 外部的“标签”,以区分我们应该从 union 中读取 char 还是 int。如果“char”在每个标签的联合中是“活动的”,并且在语义上它们是相同的,memcmp 仍然可能失败。

标签: c++ operator-overloading


【解决方案1】:

没有捷径。您必须列出所有内容。

通过引入名为tied() 的成员函数可以减少一些错误来源,例如:

struct Foo {
    A a;
    B b;
    C c;
    ...

private:
    auto tied() const { return std::tie(a, b, c, ...); }
};

这样您的operator== 就可以使用它:

bool operator==(Foo const& rhs) const { return tied() == rhs.tied(); }

这让您只列出所有成员一次。但仅此而已。您仍然必须实际列出它们(这样您仍然可以忘记一个)。


有一个提议(P0221R0)创建一个默认的operator==,但我不知道它是否会被接受。


上述提议被拒绝,支持不同的比较方向。 C++20 将允许您编写:

struct Foo {
    A a;
    B b;
    C c;

    // this just does memberwise == on each of the members
    // in declaration order (including base classes)
    bool operator==(Foo const&) const = default;
};

【讨论】:

    【解决方案2】:

    不幸的是,唯一的方法是检查所有属性。好消息是,如果您使用 && 组合所有检查,它将在第一个错误语句之后停止评估。 (短路评估)

    例如false && (4 == 4)。该程序永远不会评估4 == 4 部分,因为&& 组合的所有语句都需要true 才能得到true 作为最终结果。这有意义吗?

    【讨论】:

    • 这称为短路评估。真的没有其他选择。
    • 另外,连接在这里不是正确的词。
    • @erip 对不起。我不是母语人士。在这种情况下,用什么词比较好?
    • 连接意味着追加。我们需要前面的连词是true
    【解决方案3】:

    从 C++11 开始,引入tuples,我们还得到了std::tie()。这将允许使用从一堆变量中创建一个元组,并针对所有变量调用一个比较函数。你可以像这样使用它

    struct Foo
    {
        int a,b,c,d,e,f;
        bool operator==(const Foo& rhs) { return std::tie(a,b,c,d,e,f) == std::tie(rhs.a,rhs.b,rhs.c,rhs.d,rhs.e,rhs.f); }
    };
    

    您仍然必须列出所有要检查的成员,但这更容易。您还可以使用它使小于和大于的比较更容易。

    还应注意,变量是按照您提供给tie 的顺序检查的。这对于小于和大于比较很重要。

    std::tie(a,b) < std::tie(rhs.a, rhs.b);
    

    不必相同

    std::tie(b,a) < std::tie(rhs.b, rhs.a);
    

    【讨论】:

      【解决方案4】:

      目前没有捷径,但有计划增加对它的支持 (P0221R0)。

      Bjarne Stroustrup 最近写了一篇关于它的博客文章: A bit of background for the default comparison proposal

      在 C++14 中,没有什么比列出所有成员并比较它们更好的了,这很容易出错。引用 Bjarne 的话:

      默认比较的杀手锏实际上并不是方便,而是人们弄错了他们的相等运算符。

      【讨论】:

        【解决方案5】:

        operator== 相关的解决方案可能没有。您可以借助所谓的X-Macro 从定义表中生成相关代码。表格可能看起来像

        #define MEMBER_TBL                    \
        /*type        ,name ,default*/        \
        X(int         ,_(i) ,42     )         \
        X(float       ,_(f) ,3.14   )         \
        X(std::string ,  t  ,"Hello")         \
        

        需要_()stuff 以避免在生成std::tie() 调用时出现尾随,。确保最后一个元素是 w.o. _()。生成成员的用法是:

        struct Foo
        {
        #define _(x) x
        #define X(type, name, default) type name{default};
            MEMBER_TBL
        #undef X
        #undef _
        }
        

        这会生成:

        struct Foo
        {
            int i{42}; float f{3.14}; std::string t{"Hello"};
        }
        

        要生成operator==,您可以使用:

        bool operator==(Foo const& other) const {
                return  std::tie(
        #define _(x) x,
        #define X(type, name, default) this->name
                    MEMBER_TBL
        #undef X
                ) == std::tie(
        #define X(type, name, default) other.name
                    MEMBER_TBL
        #undef X
        #undef _
                );
            }
        

        导致

        bool operator==(Foo const& other) const {
            return std::tie(
                             this->i, this->f, this->t
            ) == std::tie(
                          other.i, other.f, other.t
            );
        }
        

        要添加新成员,您只需将新条目添加到第一个表即可。其他一切都是自动生成的。

        另一个优点是,您可以简单地添加一个dump() 方法,例如

        void print(void) const { 
            #define STR(x) #x
            #define _(x) x
            #define X(type, name, default)            \
                    std::cout <<                      \
                        STR(name) << ": " << name << " ";
                    MEMBER_TBL
            #undef X
            #undef _
            #undef STR
                    std::cout << std::endl;
                }
        

        导致

        void print() const {
            std::cout << "i" << ": " << i << " "; std::cout << "f" << ": " << f << " "; std::cout << "t" << ": " << t << " ";
            std::cout << std::endl;
        }
        

        关于成员的每一个信息都可以在一个地方(单个信息点)添加到表格中,并在需要的地方提取。

        一个工作的Demo

        【讨论】:

        • 值得一提的是,这种技术应该只用于非常预期的情况。例如,我在一个示例中使用了它,其中我有一类配置参数。每当您必须引入一个新参数时,您都必须更新代码中的几个位置,这很容易出错。恕我直言,使用 X Macro 方法简化了维护,但它仍然有点争议,所以我只推荐它作为最后的手段。但知道该技术存在,确实很有用。
        猜你喜欢
        • 2023-01-02
        • 2012-08-04
        • 1970-01-01
        • 1970-01-01
        • 2013-08-08
        • 1970-01-01
        • 1970-01-01
        • 2014-08-20
        • 1970-01-01
        相关资源
        最近更新 更多