为什么不 C++ 编译器定义了operator==和operator!=?
我非常喜欢让编译器为您做尽可能多的工作。 当编写一个简单的类时,编译器可以为您提供以下“免费”功能:
- 默认(空)构造
- 函数 复制和移动构造
- 函数 析构函数赋值
- 运算符 (
operator=
)
但它似乎无法为您提供任何比较运算符 - 例如 operator==
或 operator!=
。 例如:
class foo
{
public:
std::string str_;
int n_;
};
foo f1; // Works
foo f2(f1); // Works
foo f3;
f3 = f2; // Works
if (f3 == f2) // Fails
{ }
if (f3 != f2) // Fails
{ }
这有充分的理由吗? 为什么进行逐个成员的比较会出现问题? 显然,如果类分配内存,那么您需要小心,但是对于一个简单的类,编译器肯定可以为您执行此操作吗?
I am a big fan of letting the compiler do as much work for you as possible. When writing a simple class the compiler can give you the following for 'free':
- A default (empty) constructor
- A copy and move constructor
- A destructor
- Assignment operators (
operator=
)
But it cannot seem to give you any comparison operators - such as operator==
or operator!=
. For example:
class foo
{
public:
std::string str_;
int n_;
};
foo f1; // Works
foo f2(f1); // Works
foo f3;
f3 = f2; // Works
if (f3 == f2) // Fails
{ }
if (f3 != f2) // Fails
{ }
Is there a good reason for this? Why would performing a member-by-member comparison be a problem? Obviously if the class allocates memory then you'd want to be careful, but for a simple class surely the compiler could do this for you?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
如果编译器可以提供默认的复制构造函数,那么它应该能够提供类似的默认运算符 ==() ,这一论点是有一定道理的。 我认为决定不为该运算符提供编译器生成的默认值的原因可以通过 Stroustrup 在《C++ 的设计与演化》(第 11.4.1 节 - 复制的控制)中关于默认复制构造函数的言论来猜测。 :
因此,问题不应该是“为什么 C++ 没有默认的
operator==()
?”,而应该是“为什么 C++ 有默认的赋值和复制构造函数?”,答案是为了向后兼容 C,Stroustrup 不情愿地包含了这些项目(这可能是大多数 C++ 缺点的原因,但也可能是 C++ 流行的主要原因)。出于我自己的目的,在我的 IDE 中,我用于新类的代码片段包含私有赋值运算符和复制构造函数的声明,以便当我生成新类时,我不会得到默认赋值和复制操作 - 我必须显式删除声明如果我希望编译器能够为我生成这些操作,请从
private:
部分中获取这些操作。The argument that if the compiler can provide a default copy constructor, it should be able to provide a similar default
operator==()
makes a certain amount of sense. I think that the reason for the decision not to provide a compiler-generated default for this operator can be guessed by what Stroustrup said about the default copy constructor in "The Design and Evolution of C++" (Section 11.4.1 - Control of Copying):So instead of "why doesn't C++ have a default
operator==()
?", the question should have been "why does C++ have a default assignment and copy constructor?", with the answer being those items were included reluctantly by Stroustrup for backwards compatibility with C (probably the cause of most of C++'s warts, but also probably the primary reason for C++'s popularity).For my own purposes, in my IDE the snippet I use for new classes contains declarations for a private assignment operator and copy constructor so that when I gen up a new class I get no default assignment and copy operations - I have to explicitly remove the declaration of those operations from the
private:
section if I want the compiler to be able to generate them for me.即使在 C++20 中,编译器仍然不会为您隐式生成
operator==
但您将获得显式默认
== C++20 起:
默认
==
执行成员方式==
(与默认复制构造函数执行成员方式复制构造的方式相同)。 新规则还提供了==
和!=
之间的预期关系。 例如,使用上面的声明,我可以编写以下两个内容:此特定功能(默认
operator==
以及==
和!=
之间的对称性)来自一项提案,该提案是更广泛的语言功能的一部分,即运算符<=>
。Even in C++20, the compiler still won't implicitly generate
operator==
for youBut you will gain the ability to explicitly default
==
since C++20:Defaulting
==
does member-wise==
(in the same way that the default copy constructor does member-wise copy construction). The new rules also provide the expected relationship between==
and!=
. For instance, with the declaration above, I can write both:This specific feature (defaulting
operator==
and symmetry between==
and!=
) comes from one proposal that was part of the broader language feature that isoperator<=>
.编译器不知道您是否想要指针比较或深度(内部)比较。
更安全的做法是不实现它并让程序员自己实现。 然后他们就可以做出他们喜欢的所有假设。
The compiler wouldn't know whether you wanted a pointer comparison or a deep (internal) comparison.
It's safer to just not implement it and let the programmer do that themselves. Then they can make all the assumptions they like.
恕我直言,没有“充分”的理由。 之所以有这么多人同意这个设计决策,是因为他们没有学会掌握基于值的语义的力量。 人们需要编写大量自定义复制构造函数、比较运算符和析构函数,因为他们在实现中使用原始指针。
当使用适当的智能指针(如 std::shared_ptr)时,默认的复制构造函数通常很好,并且假设的默认比较运算符的明显实现也很好。
IMHO, there is no "good" reason. The reason there are so many people that agree with this design decision is because they did not learn to master the power of value-based semantics. People need to write a lot of custom copy constructor, comparison operators and destructors because they use raw pointers in their implementation.
When using appropriate smart pointers (like std::shared_ptr), the default copy constructor is usually fine and the obvious implementation of the hypothetical default comparison operator would be as fine.
它的答案是 C++ 没有做 == 因为 C 没有,这就是为什么 C 只提供默认 = 而没有 == 的原因。
C 希望保持简单:
C 实现 = 由 memcpy 实现; 然而,由于填充的原因, == 无法通过 memcmp 实现。
因为 padding 没有初始化,所以 memcmp 说它们是不同的,即使它们是相同的。
空类也存在同样的问题:memcmp 说它们不同,因为空类的大小不为零。
从上面可以看出,在C中实现==比实现=更复杂。
有关于此的一些代码示例。
如果我错了,请您指正。
It's answered C++ didn't do == because C didn't, and here is why C provides only default = but no == at first place.
C wanted to keep it simple:
C implemented = by memcpy; however, == cannot be implemented by memcmp due to padding.
Because padding is not initialized, memcmp says they are different even though they are the same.
The same problem exists for empty class: memcmp says they are different because size of empty classes are not zero.
It can be seen from above that implementing == is more complicated than implementing = in C.
Some code example regarding this.
Your correction is appreciated if I'm wrong.
在此视频中,创作者 Alex Stepanov STL 在 13:00 左右回答了这个问题。 总而言之,在观察了 C++ 的演变之后,他认为:
然后他说在(遥远的)未来 == 和 != 将隐式生成。
In this video Alex Stepanov, the creator of STL addresses this very question at about 13:00. To summarize, having watched the evolution of C++ he argues that:
He then says that in the (distant) future == and != will be implicitly generated.
C++20 提供了一种轻松实现默认比较运算符的方法。
来自 cppreference.com 的示例:
C++20 provides a way to easily implement a default comparison operator.
Example from cppreference.com:
无法定义默认
==
,但您可以通过==
定义默认!=
,通常您应该自己定义。为此,您应该执行以下操作:
您可以看到 http://www.cplusplus.com /reference/std/utility/rel_ops/ 了解详细信息。
另外,如果您定义
operator<
,使用std::rel_ops
时可以从中推导出<=、>、>=的运算符。但在使用 std::rel_ops 时应该小心,因为比较运算符可能会被推断为您不期望的类型。
从基本运算符推导相关运算符的更优选方法是使用 boost: :运算符。
boost 中使用的方法更好,因为它只为您想要的类定义运算符的用法,而不是为范围内的所有类定义运算符的用法。
您还可以从“+=”生成“+”,从“-=”生成-,等等(请参阅完整列表此处)
It is not possible to define default
==
, but you can define default!=
via==
which you usually should define yourselves.For this you should do following things:
You can see http://www.cplusplus.com/reference/std/utility/rel_ops/ for details.
In addition if you define
operator<
, operators for <=, >, >= can be deduced from it when usingstd::rel_ops
.But you should be careful when you use
std::rel_ops
because comparison operators can be deduced for the types you are not expected for.More preferred way to deduce related operator from basic one is to use boost::operators.
The approach used in boost is better because it define the usage of operator for the class you only want, not for all classes in scope.
You can also generate "+" from "+=", - from "-=", etc... (see full list here)
C++0x
has有默认函数的提案,因此您可以说default operator==;
我们了解到,让这些事情变得明确是有帮助的。
C++0x
hashad a proposal for default functions, so you could saydefault operator==;
We've learnt that it helps to make these things explicit.
从概念上来说,定义平等并不容易。 即使对于 POD 数据,人们也可能会争辩说,即使字段相同,但它是不同的对象(位于不同的地址),它也不一定相等。 这其实取决于操作者的使用情况。 不幸的是,你的编译器没有通灵能力,无法推断出这一点。
除此之外,默认功能也是搬起石头砸自己脚的好方法。 您描述的默认值基本上是为了保持与 POD 结构的兼容性。 然而,它们确实造成了足够多的破坏,导致开发人员忘记它们或默认实现的语义。
Conceptually it is not easy to define equality. Even for POD data, one could argue that even if the fields are the same, but it is a different object (at a different address) it is not necessarily equal. This actually depends on the usage of the operator. Unfortunately your compiler is not psychic and cannot infer that.
Besides this, default functions are excellent ways to shoot oneself in the foot. The defaults you describe are basically there to keep compatibility with POD structs. They do however cause more than enough havoc with developers forgetting about them, or the semantics of the default implementations.
只是为了让这个问题的答案随着时间的流逝而保持完整:从 C++20 开始,它可以使用命令
auto operator<=>(const foo&) const = default;
自动生成它将生成所有运算符:==、!=、<、<=、> 和 >=,请参阅 https://en.cppreference.com/w/cpp/language/default_comparisons 了解详细信息。
由于操作员的外观
<=>
,所以被称为飞船操作员。 另请参阅为什么我们需要宇宙飞船<=> C++ 中的运算符?。编辑:也在 C++11 中,可以使用
std::tie
来替代它,请参阅 https://en.cppreference.com/w/cpp/utility/tuple/tie 使用bool 运算符<(…)< 的完整代码示例/代码>。 有趣的部分是,更改为与
==
一起使用:std::tie
可与所有比较运算符一起使用,并且由编译器完全优化。Just so that the answers to this question remains complete as the time passes by: since C++20 it can be automatically generated with command
auto operator<=>(const foo&) const = default;
It will generate all the operators: ==, !=, <, <=, >, and >=, see https://en.cppreference.com/w/cpp/language/default_comparisons for details.
Due to operator's look
<=>
, it is called a spaceship operator. Also see Why do we need the spaceship <=> operator in C++?.EDIT: also in C++11 a pretty neat substitute for that is available with
std::tie
see https://en.cppreference.com/w/cpp/utility/tuple/tie for a complete code example withbool operator<(…)
. The interesting part, changed to work with==
is:std::tie
works with all comparison operators, and is completely optimized away by the compiler.从功能上来说,这可能不是问题,但就性能而言,默认的逐个成员比较可能比默认的逐个成员分配/复制更次优。 与分配顺序不同,比较顺序会影响性能,因为第一个不相等的成员意味着可以跳过其余成员。 因此,如果有一些通常相等的成员,您希望最后比较它们,而编译器不知道哪些成员更有可能相等。
考虑这个例子,其中 verboseDescription 是从相对较小的一组可能的天气描述中选择的长字符串。
(当然,如果编译器认识到它们没有副作用,则它有权忽略比较的顺序,但大概它仍然会从源代码中获取其 que,因为它自己没有更好的信息。)
It may not be a problem functionally, but in terms of performance, default member-by-member comparison is liable to be more sub-optimal than default member-by-member assignment/copying. Unlike order of assignment, order of comparison impacts performance because the first unequal member implies the rest can be skipped. So if there are some members that are usually equal you want to compare them last, and the compiler doesn't know which members are more likely to be equal.
Consider this example, where
verboseDescription
is a long string selected from a relatively small set of possible weather descriptions.(Of course the compiler would be entitled to disregard the order of comparisons if it recognizes that they have no side-effects, but presumably it would still take its que from the source code where it doesn't have better information of its own.)
我同意,对于 POD 类型类,编译器可以为你做这件事。 然而,您可能认为简单的编译器可能会出错。 所以还是让程序员来做比较好。
我曾经遇到过一个 POD 案例,其中两个字段是唯一的 - 因此永远不会认为比较是正确的。 然而,我只需要在有效负载上进行比较——这是编译器永远无法理解或无法自行弄清楚的。
此外——他们写起来并不需要很长时间,不是吗?
I agree, for POD type classes then the compiler could do it for you. However what you might consider simple the compiler might get wrong. So it is better to let the programmer do it.
I did have a POD case once where two of the fields were unique - so a comparison would never be considered true. However the comparison I needed only ever compared on the payload - something the compiler would never understand or could ever figure out on it's own.
Besides - they don't take long to write do they?!