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

时间:2008-10-20 09:42:36

标签: c++ operators

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

  • 默认(空)构造函数
  • 复制构造函数
  • 析构函数
  • 赋值运算符(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
{ }

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

13 个答案:

答案 0 :(得分:288)

如果编译器可以提供默认的复制构造函数,它应该能够提供类似的默认operator==()使得一定的意义。我认为决定不为此运算符提供编译器生成的默认值的原因可以通过Stroustrup所说的关于“C ++的设计和演变”中的默认复制构造函数的猜测(第11.4.1节 - 复制控制) :

  

我个人认为这很不幸   复制操作由。定义   默认,我禁止复制   我班上很多课的对象。   但是,C ++继承了它的默认值   赋值和复制构造函数   C,经常使用它们。

因此,问题应该是“为什么C ++有一个默认的赋值和复制构造函数?”而不是“为什么C ++没有默认的operator==()?”,答案是包含那些项目Stroustrup不情愿地向后兼容C(可能是大多数C ++瑕疵的原因,但也可能是C ++流行的主要原因)。

出于我自己的目的,在我的IDE中,我用于新类的片段包含私有赋值运算符和复制构造函数的声明,这样当我生成一个新类时,我没有默认赋值和复制操作 - 我必须明确如果我希望编译器能够为我生成这些操作,请从private:部分删除这些操作的声明。

答案 1 :(得分:70)

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

不执行它并让程序员自己完成它会更安全。然后他们可以做出他们喜欢的所有假设。

答案 2 :(得分:65)

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

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

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

但您将获得显式默认==的能力:

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==以及==!=之间的对称)来自one proposaloperator<=>是{{3}}更广泛的语言功能的一部分

答案 3 :(得分:42)

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

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

答案 4 :(得分:36)

回答C ++没有做= =因为C没有,这就是为什么C首先只提供default =但是没有==。 C希望保持简单: C实现= memcpy;但是,由于填充,memcmp无法实现==。 因为padding没有初始化,所以memcmp说它们是不同的,即使它们是相同的。 空类存在同样的问题:memcmp表示它们不同,因为空类的大小不为零。 从上面可以看出,实现==比在C中实现=更复杂。 有关此问题的一些代码example。 如果我错了,你会得到更正。

答案 5 :(得分:24)

在这个video Alex Stepanov中,STL的创建者在大约13:00解决了这个问题。总而言之,他观察了C ++的演变,他认为:

  • 遗憾的是, ==和!= 未被隐含声明(Bjarne同意他的意见)。一个正确的语言应该为你准备好这些东西(他进一步建议你不应该定义!= 打破 == 的语义)< / LI>
  • 这种情况的原因有其根源(与C ++中的许多C ++问题一样)。在那里,赋值运算符是用逐位赋值隐式定义的,但这不适用于<强> == 即可。更详细的解释可以在Bjarne Stroustrup的article中找到。
  • 在后续问题中为什么当时不是成员比较的成员他说令人惊奇的事情:C是一种本土语言和实施者这些东西让里奇告诉他,他发现这很难实现!

然后他说,在(遥远的)未来, == != 将被隐式生成。

答案 6 :(得分:15)

无法定义默认==,但您可以通过!=定义默认==,您通常应该定义它们。 为此你应该做以下事情:

#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时,可以从中推断出&lt; =,&gt;,&gt; =的运算符。

但是当你使用std::rel_ops时应该小心,因为可以推导出比较运算符用于你不期望的类型。

从基本运算符中推导出相关运算符的更优选方法是使用boost::operators

boost中使用的方法更好,因为它为您只想要的类定义运算符的用法,而不是范围内的所有类。

您还可以从“+ =”生成“+”, - 从“ - =”等生成...(参见完整列表here

答案 7 :(得分:13)

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

来自cppreference.com的示例:

INSTALLED_APPS

答案 8 :(得分:10)

C ++ 0x 有一个默认函数的提议,所以你可以说default operator==; 我们已经了解到明确这些内容是有帮助的。

答案 9 :(得分:5)

从概念上讲,定义平等并不容易。即使对于POD数据,人们也可以争辩说,即使字段相同,但它是不同的对象(在不同的地址),它也不一定相等。这实际上取决于运营商的使用情况。不幸的是,你的编译器不是通灵的,也无法推断出来。

除此之外,默认功能是拍摄脚部的绝佳方式。您描述的默认值基本上是为了保持与POD结构的兼容性。然而,它们确实会导致开发人员忘记它们或者默认实现的语义而造成的破坏。

答案 10 :(得分:1)

  

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

在功能上可能不是问题,但就性能而言,默认的逐个成员比较可能比默认的成员分配/复制更不理想。与赋值顺序不同,比较顺序会影响性能,因为第一个不相等的成员意味着可以跳过其余部分。因此,如果有一些成员通常是相同的,那么你想最后比较它们,并且编译器不知道哪些成员更可能是平等的。

考虑这个例子,其中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
    }
}

(当然,如果编译器认识到它们没有副作用,则有权忽略比较的顺序,但是可能它仍然会从源代码中获取它没有更好的信息。它本身。)

答案 11 :(得分:1)

只是为了使问题的答案随着时间的流逝而保持完整:自C ++ 20起,它可以使用命令auto operator<=>(const foo&) const = default;自动生成

它将生成所有运算符:==,!=,<,<=,>和> =,有关详细信息,请参见https://en.cppreference.com/w/cpp/language/default_comparisons

由于运营商的外观<=>,它被称为太空飞船运营商。另请参见Why do we need the spaceship <=> operator in C++?

编辑:在C ++ 11中,std::tie也提供了一个相当好的替代品,有关bool operator<(…)的完整代码示例,请参见https://en.cppreference.com/w/cpp/utility/tuple/tie。与==一起使用的有趣部分是:

#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与所有比较运算符一起使用,并且已被编译器完全优化。

答案 12 :(得分:-1)

我同意,对于POD类型类,编译器可以为您完成。但是你可能认为编译器可能会出错。所以最好让程序员去做。

我确实有一个POD案例,其中两个字段是唯一的 - 所以比较永远不会被认为是真的。然而,我只需要在有效载荷上进行比较 - 编译器永远无法理解或者无法自行解决的问题。

此外 - 他们不需要很长时间才能写出来吗?!

相关问题