为什么C ++编译器不定义运算符==和运算符!=?

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

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

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

编译器不知道你是想要一个指针比较还是深入的(内部)比较。

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

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

我个人认为不幸的是,复制操作是默认定义的,我禁止复制我的许多课程的对象。 但是,C ++从Cinheritance了它的默认赋值和拷贝构造函数,并且它们经常被使用。

因此,而不是“为什么不C ++有一个默认的operator==() ?”,问题应该是“为什么C ++有一个默认的赋值和复制构造函数?”,答案是这些项目被包括在勉强Stroustrup向后兼容C(可能是大多数C ++的瑕疵的原因,但也可能是C ++普及的主要原因)。

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

更新2:不幸的是,这个提议并没有使它成为C ++ 17 ,所以现在在这方面没有任何改变。

更新:当前版本的提案很有可能被投票到C + + 17在这里 。

最近有一个关于显式默认比较运算符的build议(N4126) ,得到了标准委员会的非常积极的反馈,所以希望我们能以某种forms在C ++ 17中看到它。

总之,build议的语法是:

 struct Thing { int a, b, c; std::string d; }; bool operator==(const Thing &, const Thing &)= default; bool operator!=(const Thing &, const Thing &)= default; 

或以私人领域的课程的friendforms:

 class Thing { int a, b; friend bool operator<(Thing, Thing) = default; friend bool operator>(Thing, Thing) = default; friend bool operator<=(Thing, Thing) = default; friend bool operator>=(Thing, Thing) = default; }; 

甚至简而言之:

 struct Thing { int a, b, c; std::string d; default: ==, !=, <, >, <=, >=; // defines the six non-member functions }; 

当然,这个提议最终被接受的时候,这一切都可能会改变。

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

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

答案是C ++没有做,因为C没有,这就是为什么C提供只有默认=,但没有在第一个地方。 C想保持简单:C实现=通过memcpy; 但是,由于填充,==不能由memcmp实现。 因为填充没有初始化,所以memcmp说它们是不同的,即使它们是相同的。 空类也存在同样的问题:memcmp说它们是不同的,因为空类的大小不为零。 从上面可以看出,实现==比在C中实现=要复杂一些。 如果我错了,你的更正是值得赞赏的。

定义默认==是不可能的,但是你可以定义默认的!= via == ,你通常应该定义自己。 为此,你应该做以下的事情:

 #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时,你应该小心,因为比较运算符可以被推断为你不期望的types。

从基本algorithm推导出相关算子的更好的方法是使用boost :: operators 。

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

你也可以从“+ =”生成“+”, – 从“ – =”等等(见这里的完整列表)

在这段video中 ,STL的创build者Alex Stepanov在13:00左右解决了这个问题。 总而言之,在观察了C ++的发展之后,他认为:

  • 不幸的是, ==和!=没有被隐式声明(而Bjarne也同意他)。 一个正确的语言应该为你准备好这些东西(他进一步build议你不应该能够定义!=打破==的语义)
  • 这是这种情况的原因有其根源(C ++中的许多问题)在C那里,赋值运算符是隐式定义的逐位分配,但不会为==
  • 在接下来的问题中, 为什么不是成员通过成员比较使用,他说了一个了不起的事情 :C是一种本土语言,为Ritchie实施这些东西的人告诉他,他发现这很难实现!

然后他说,在遥远的未来, ==!=将会被隐含地生成。

C ++ 0x 一个默认函数的build议,所以你可以说default operator==; 我们知道,这有助于明确这些事情。

只是一个注释,也由编译器免费提供:

  • 操作员新
  • operator new []
  • 操作员删除
  • 操作员删除[]

从概念上说,定义平等并不容易。 即使是POD数据,也可以争辩说,即使字段是相同的,但它是一个不同的对象(在不同的地址),它不一定是平等的。 这实际上取决于操作员的使用情况。 不幸的是,你的编译器不是精神的,不能推断。

除此之外,默认的function是在脚下自拍的好方法。 你所描述的默认值基本上是保持与POD结构的兼容性。 然而,他们对开发者忘记了这些,或者默认实现的语义造成了太大的破坏。

我同意,对于PODtypes的类,然后编译器可以为你做。 然而,你可能认为简单的编译器可能会出错。 所以最好让程序员去做。

我确实有一个POD案例,其中两个领域是独一无二的 – 所以一个比较永远不会被认为是真实的。 然而,我只需要在有效载荷上进行比较的比较 – 编译器永远不会理解或者无法想象的东西。

此外 – 他们不需要很长时间写呢?!

这是否有很好的理由? 为什么执行会员逐一比较是一个问题?

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

考虑这个例子,其中verboseDescription是从相对较小的一组可能的天气描述中select的一个长string。

 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 } } 

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

默认的比较操作符是正确的,只有很less的时间; 我期望他们会成为问题的根源,而不是有用的东西。

另外,你提到的默认方法往往是不可取的。 看到这样的代码来摆脱默认的拷贝构造函数和operator =是非常普遍的:

 class NonAssignable { // .... private: NonAssignable(const NonAssignable&); // Unimplemented NonAssignable& operator=(const NonAssignable&); // Unimplemented }; 

在很多代码中,通常会看到一个注释“default copy constructor and operator = OK”,以表明它们被删除或明确定义并不是一个错误。