为什么不 C++ 编译器定义了operator==和operator!=?

发布于 2024-07-06 18:57:40 字数 559 浏览 9 评论 0原文

我非常喜欢让编译器为您做尽可能多的工作。 当编写一个简单的类时,编译器可以为您提供以下“免费”功能:

  • 默认(空)构造
  • 函数 复制和移动构造
  • 函数 析构函数赋值
  • 运算符 (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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

遗心遗梦遗幸福 2024-07-13 18:57:40

如果编译器可以提供默认的复制构造函数,那么它应该能够提供类似的默认运算符 ==() ,这一论点是有一定道理的。 我认为决定不为该运算符提供编译器生成的默认值的原因可以通过 Stroustrup 在《C++ 的设计与演化》(第 11.4.1 节 - 复制的控制)中关于默认复制构造函数的言论来猜测。 :

我个人认为这是不幸的
复制操作定义为
默认并且我禁止复制
我的许多课程的对象。
然而,C++继承了它的默认值
赋值和复制构造函数来自
C,并且它们经常被使用。

因此,问题不应该是“为什么 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):

I personally consider it unfortunate
that copy operations are defined by
default and I prohibit copying of
objects of many of my classes.
However, C++ inherited its default
assignment and copy constructors from
C, and they are frequently used.

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.

£冰雨忧蓝° 2024-07-13 18:57:40

即使在 C++20 中,编译器仍然不会为您隐式生成 operator==

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

但您将获得显式默认 == C++20 起

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

默认 == 执行成员方式 == (与默认复制构造函数执行成员方式复制构造的方式相同)。 新规则还提供了 ==!= 之间的预期关系。 例如,使用上面的声明,我可以编写以下两个内容:

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

此特定功能(默认 operator== 以及 ==!= 之间的对称性)来自一项提案,该提案是更广泛的语言功能的一部分,即运算符<=>

Even in C++20, the compiler still won't implicitly generate operator== for you

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

But you will gain the ability to explicitly default == since C++20:

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

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:

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

This specific feature (defaulting operator== and symmetry between == and !=) comes from one proposal that was part of the broader language feature that is operator<=>.

风吹短裙飘 2024-07-13 18:57:40

编译器不知道您是否想要指针比较或深度(内部)比较。

更安全的做法是不实现它并让程序员自己实现。 然后他们就可以做出他们喜欢的所有假设。

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.

染柒℉ 2024-07-13 18:57:40

恕我直言,没有“充分”的理由。 之所以有这么多人同意这个设计决策,是因为他们没有学会掌握基于值的语义的力量。 人们需要编写大量自定义复制构造函数、比较运算符和析构函数,因为他们在实现中使用原始指针。

当使用适当的智能指针(如 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.

最佳男配角 2024-07-13 18:57:40

它的答案是 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.

云淡月浅 2024-07-13 18:57:40

在此视频中,创作者 Alex Stepanov STL 在 13:00 左右回答了这个问题。 总而言之,在观察了 C++ 的演变之后,他认为:

  • 不幸的是 == 和 != 没有被隐式声明(Bjarne 同意他的观点)。 正确的语言应该为您准备好这些东西(他进一步建议您不应该定义一个破坏 == 语义的 !=
  • )这种情况的原因在 C 中有其根源(与许多 C++ 问题一样)。其中,赋值运算符是通过逐位赋值隐式定义的,但这不适用于 == 。 可以在此找到更详细的解释 Bjarne Stroustrup 的文章
  • 在接下来的问题中为什么不使用成员之间的比较,他说了一件令人惊奇的事情: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:

  • It's unfortunate that == and != are not implicitly declared (and Bjarne agrees with him). A correct language should have those things ready for you (he goes further on to suggest you should not be able to define a != that breaks the semantics of ==)
  • The reason this is the case has its roots (as many of C++ problems) in C. There, the assignment operator is implicitly defined with bit by bit assignment but that wouldn't work for ==. A more detailed explanation can be found in this article from Bjarne Stroustrup.
  • In the follow up question Why then wasn't a member by member comparison used he says an amazing thing : C was kind of a homegrown language and the guy implementing these stuff for Ritchie told him he found this to be hard to implement!

He then says that in the (distant) future == and != will be implicitly generated.

泛滥成性 2024-07-13 18:57:40

C++20 提供了一种轻松实现默认比较运算符的方法。

来自 cppreference.com 的示例:

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>

C++20 provides a way to easily implement a default comparison operator.

Example from cppreference.com:

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>
骄兵必败 2024-07-13 18:57:40

无法定义默认 ==,但您可以通过 == 定义默认 !=,通常您应该自己定义。
为此,您应该执行以下操作:

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

您可以看到 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:

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

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 using std::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)

橪书 2024-07-13 18:57:40

C++0x has 有默认函数的提案,因此您可以说 default operator==;
我们了解到,让这些事情变得明确是有帮助的。

C++0x has had a proposal for default functions, so you could say default operator==;
We've learnt that it helps to make these things explicit.

神妖 2024-07-13 18:57:40

从概念上来说,定义平等并不容易。 即使对于 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.

写给空气的情书 2024-07-13 18:57:40

只是为了让这个问题的答案随着时间的流逝而保持完整:从 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 运算符<(…)< 的完整代码示例/代码>。 有趣的部分是,更改为与 == 一起使用:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

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 with bool operator<(…). The interesting part, changed to work with == is:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

std::tie works with all comparison operators, and is completely optimized away by the compiler.

第几種人 2024-07-13 18:57:40

这有充分的理由吗? 为什么执行逐个成员比较会出现问题?

从功能上来说,这可能不是问题,但就性能而言,默认的逐个成员比较可能比默认的逐个成员分配/复制更次优。 与分配顺序不同,比较顺序会影响性能,因为第一个不相等的成员意味着可以跳过其余成员。 因此,如果有一些通常相等的成员,您希望最后比较它们,而编译器不知道哪些成员更有可能相等。

考虑这个例子,其中 verboseDescription 是从相对较小的一组可能的天气描述中选择的长字符串。

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(当然,如果编译器认识到它们没有副作用,则它有权忽略比较的顺序,但大概它仍然会从源代码中获取其 que,因为它自己没有更好的信息。)

Is there a good reason for this? Why would performing a member-by-member comparison be a problem?

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.

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(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.)

梦行七里 2024-07-13 18:57:40

我同意,对于 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?!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文