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.
我必须在 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.