C++ equal(==) 重载、快捷方式或比较所有属性的最佳方式

C++ equal(==) overload, Shortcut or best way comparing all attributes

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

是否有比较 class 中每个属性的快捷方式?

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

可以通过引入名为 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;
};

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

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

从 C++11 开始引入 tuples we also got 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);

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

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

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

The killer argument for default comparisons is not actually convenience, but the fact that people get their equality operators wrong.

有一个与operator==不相关的可能解决方案。您可以在所谓的 X-Macro 的帮助下从定义 table 生成相关代码。 table 可能看起来像

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

需要 _() 内容以避免在生成 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
    );
}

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

另一个优点是,您可以简单地添加一个 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;
}

有关成员的所有信息都可以添加到 table 的一个位置(单点信息),并在需要的其他地方提取。

工作 Demo.