为什么C ++编译器没有定义operator ==和operator!=?

Why don't C++ compilers define operator== and operator!=?

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

  • 默认(空)构造函数
  • 复制构造函数
  • 析构函数
  • 赋值运算符(operator=)

但它似乎不能给您任何比较运算符-如operator==operator!=。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
{ }

这有充分的理由吗?为什么执行成员间比较是一个问题?显然,如果类分配了内存,那么您需要小心,但是对于一个简单的类,编译器肯定可以为您做到这一点?


如果编译器可以提供一个默认的复制构造函数,那么它应该能够提供一个类似的默认operator==(),这一论点有一定的意义。我认为,不为编译器提供编译器生成的默认值的原因可以通过Stroustrup关于"C++设计和演化"中的默认复制构造函数(第114.1-复制控制)来猜出:

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.

因此,而不是"为什么C++不具有默认的EDCOX1,7?"问题应该是"C++为什么有一个默认的赋值和复制构造函数?"答案是,这些项目被Stroustrup勉强地包含在与C的向后兼容性(可能是大多数C++疣的原因,但也可能是C++流行的主要原因)。

为了我自己的目的,在我的IDE中,我用于新类的代码片段包含了一个私有赋值运算符和复制构造函数的声明,这样当我生成一个新类时,就不会得到默认的赋值和复制操作-如果我希望编译器能够为我生成它们。


编译器不知道您是希望进行指针比较还是进行深度(内部)比较。

不实现它,让程序员自己来实现它是比较安全的。然后他们可以做出他们喜欢的所有假设。


即使在C++ 20中,编译器也不会隐式地为您隐式生成EDCOX1 OR 0。

1
2
3
4
5
6
7
struct foo
{
    std::string str;
    int n;
};

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

但您将获得明确违约的能力==

1
2
3
4
5
6
7
8
9
10
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;
};

默认的==执行成员级的==(与默认的复制构造函数执行成员级的复制构造相同)。新规则还提供了==!=之间的预期关系。例如,通过上面的声明,我可以同时编写:

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

这个特定的特征(默认的operator====!=之间的对称性)来自一个提议,它是operator<=>这一更广泛语言特征的一部分。


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

当使用适当的智能指针(如std::shared_ptr)时,默认的复制构造函数通常很好,并且假设的默认比较运算符的明显实现也很好。


它回答C++没有做= =,因为C没有,这就是为什么C只提供默认= =但NO= =在第一位。C想要保持简单:c由memcpy实现=但是,由于填充的原因,memcmp无法实现=。因为padding没有初始化,memcmp说它们是不同的,即使它们是相同的。空类也存在同样的问题:memcmp说它们是不同的,因为空类的大小不是零。从上面可以看出,在C中实现==比实现==更复杂。关于这个的一些代码示例。如果我错了,请你改正。


在这段视频中,STL的创建者亚历克斯·斯蒂芬诺夫在13:00左右就解决了这个问题。综上所述,他观察了C++的进化,认为:

  • 不幸的是,==和!=未明确声明(且比亚恩同意)。一个正确的语言应该为你准备好这些东西(他进一步建议你不应该定义一个!=这打破了==)的语义
  • 这种情况的原因在C.中有其根源(如C++中的许多问题),赋值运算符隐式地定义为逐位赋值,但对于==不适用。在本文中可以从bjarne stroustrup找到更详细的解释。
  • 在接下来的问题中,为什么没有一个成员一个成员的比较,他说了一件令人惊奇的事情:C是一种本土语言,为里奇实现这些东西的人告诉他,他发现这很难实现!

然后他说在遥远的将来==和!=将隐式生成。


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

1
2
3
4
5
6
7
8
9
10
11
#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::operators。

Boost中使用的方法更好,因为它为您只需要的类定义了操作符的用法,而不是为作用域中的所有类。

您还可以从"+=",-从"-="等生成"+"…(请参阅完整列表)


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

来自cppreference.com的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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++0x有有默认函数的建议,所以可以说EDOCX1>0我们已经知道,这有助于明确这些事情。


从概念上讲,定义平等并不容易。即使对于pod数据,也可以认为即使字段相同,但它是不同的对象(在不同的地址),也不一定相等。这实际上取决于运算符的用法。不幸的是,你的编译器不是通灵的,不能推断出这一点。

除此之外,默认函数是一种很好的方式来射自己的脚。您描述的默认设置基本上是为了保持与pod结构的兼容性。但是,它们确实会对开发人员忘记它们或默认实现的语义造成足够大的破坏。


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

这在功能上可能不是问题,但在性能方面,默认成员之间的比较可能比默认成员之间的分配/复制更加次优。与分配顺序不同,比较顺序影响性能,因为第一个不相等的成员意味着可以跳过其余的成员。因此,如果有一些成员通常是相等的,那么您最后要比较它们,而编译器不知道哪些成员更可能是相等的。

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

1
2
3
4
5
6
7
8
9
10
11
12
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。)


我同意,对于pod类型的类,编译器可以为您做这件事。然而,您可能认为简单的编译器可能会出错。所以最好让程序员来做。

我曾经有过一个pod案例,其中两个字段是唯一的,所以一个比较永远不会被认为是真的。然而,我只需要在有效负载上进行比较——这是编译器永远无法理解或自己也无法理解的。

另外,他们不需要花很长时间来写作,是吗?!