我非常喜欢让编译器为您做尽可能多的工作。当编写一个简单的类时,编译器可以为您"免费"提供以下内容:
- 默认(空)构造函数
- 复制构造函数
- 析构函数
- 赋值运算符(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
{ } |
这有充分的理由吗?为什么执行成员间比较是一个问题?显然,如果类分配了内存,那么您需要小心,但是对于一个简单的类,编译器肯定可以为您做到这一点?
- 当然,析构函数也是免费提供的。
- Alex Stepanov在最近的一次谈话中指出,没有默认的自动==是错误的,就像在某些条件下有默认的自动分配(=)。(关于指针的争论不一致,因为逻辑既适用于=也适用于==,而不仅仅适用于第二个)。
- @Becko It是A9:youtube.com/watch系列中的一个?V=K-Melqayp5y,我不记得在哪次会谈中。还有一个建议,它似乎正在向C++ 17开放的STD.Org/JTC1/SC22/WG21/DOCS/PoSs/2016/P0221R0.HTML
- @Becko,它是YouTube A9"高效组件编程"系列或"编程对话"系列中的第一款。
- @Becko实际上下面有一个答案指向了Alex的观点stackoverflow.com/a/23329089/225186
- 请参阅C++ 20信息的答案:StasOfFuff.COM/A/50345 359
如果编译器可以提供一个默认的复制构造函数,那么它应该能够提供一个类似的默认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++ 11中,不是把赋值操作符和复制构造函数私有化,而是可以像这样删除它们:EDCOX1、10和EDCX1 11。
- "但是,C++继承了它的默认赋值,并从C复制构造函数",这并不意味着为什么必须用这种方式制作所有C++类型。他们应该把这个限制在普通的旧豆荚上,仅仅是C语言中的类型,不再是了。
- 我当然可以理解为什么C++继承了EDOCX1 12的这些行为,但是我希望它让EDCOX1 13的行为不同(SLALY)。在这个过程中,除了默认访问之外,它还将在struct和class之间提供一个更有意义的区别。
编译器不知道您是希望进行指针比较还是进行深度(内部)比较。
不实现它,让程序员自己来实现它是比较安全的。然后他们可以做出他们喜欢的所有假设。
- 这个问题并不能阻止它生成一个拷贝ctor,因为它是非常有害的。
- 复制构造函数与比较运算符在完全不同的上下文中使用。而且,imho,它的背景很清楚它在做什么。
- 存在与C:C89兼容的问题:为模仿C++分配的结构生成代码(可能复制构造函数…)我得检查一下)。因此,正常的C++生成类似的代码。
- 复制构造函数(和operator=通常在与比较运算符相同的上下文中工作——也就是说,在执行a = b之后,a == b是正确的。编译器使用与operator=相同的聚合值语义提供默认operator==显然是有意义的。我怀疑paercebal在这里是正确的,因为EDOCX1(和copy ctor)只提供C兼容性,他们不想让情况变得更糟。
- - 1。当然,您需要一个深入的比较,如果程序员想要一个指针比较,他会写(&;f1==amp;f2)
- 维克多,我建议你重新考虑你的反应。如果类foo包含bar*,那么编译器如何知道foo::operator==是要比较bar*的地址还是bar的内容?
- @标记:如果包含指针,比较指针值是合理的-如果包含值,比较值是合理的。在特殊情况下,程序员可以重写。这就像语言实现了ints和ints指针之间的比较。
- -1正如其他人所指出的,与运算符相比,这是不一致的论点。=
- 上述理由均无效。如果编译器说编译器必须提供一个默认的"操作符==",比较每个字段值,那将不是C++标准中最疯狂的事情。见鬼,如果我们需要针对指针的安全性,只需在这种情况下发出警告或错误。
- @马金格拉姆:你的话毫无意义。编译器实现的运算符==只应在所有成员(如果可以访问)上调用运算符==。例如,克隆体不会破坏深链。std::vector也不会。然而,指针和智能指针比较起来比较起来比较浅。
- 编译器不需要弄明白这一点。两个选项中的一个可以指定为标准,那么编译器供应商就必须遵循该标准。当然,最明显的选择是指针的浅比较和成员的值比较,这正是v.oddou所描述的。
即使在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<=>这一更广泛语言特征的一部分。
- 你知道最近是否有更新吗?它将在C++ 17中可用吗?
- @ DCMM88很幸运,它在C++ 17中是不可用的。我已经更新了答案。
- 允许相同的事物(除了短形式)的修改建议将在C++ 20中:
嗯,没有"好"的理由。之所以有这么多人同意这个设计决策,是因为他们没有学会掌握基于值的语义的力量。人们需要编写许多自定义的复制构造函数、比较运算符和析构函数,因为它们在实现中使用原始指针。
当使用适当的智能指针(如std::shared_ptr)时,默认的复制构造函数通常很好,并且假设的默认比较运算符的明显实现也很好。
它回答C++没有做= =,因为C没有,这就是为什么C只提供默认= =但NO= =在第一位。C想要保持简单:c由memcpy实现=但是,由于填充的原因,memcmp无法实现=。因为padding没有初始化,memcmp说它们是不同的,即使它们是相同的。空类也存在同样的问题:memcmp说它们是不同的,因为空类的大小不是零。从上面可以看出,在C中实现==比实现==更复杂。关于这个的一些代码示例。如果我错了,请你改正。
- C++不使用EMCOX1×16的MEMCPY,它只适用于POD类型,但是C++也为非POD类型提供了默认的EDCOX1(16)。
- 是的,C++是用更复杂的方式实现的。似乎C只是用一个简单的memcpy实现了。
- 这个答案的内容应该和迈克尔的答案放在一起。他的回答纠正了问题,然后这个回答了问题。
在这段视频中,STL的创建者亚历克斯·斯蒂芬诺夫在13:00左右就解决了这个问题。综上所述,他观察了C++的进化,认为:
- 不幸的是,==和!=未明确声明(且比亚恩同意)。一个正确的语言应该为你准备好这些东西(他进一步建议你不应该定义一个!=这打破了==)的语义
- 这种情况的原因在C.中有其根源(如C++中的许多问题),赋值运算符隐式地定义为逐位赋值,但对于==不适用。在本文中可以从bjarne stroustrup找到更详细的解释。
- 在接下来的问题中,为什么没有一个成员一个成员的比较,他说了一件令人惊奇的事情:C是一种本土语言,为里奇实现这些东西的人告诉他,他发现这很难实现!
然后他说在遥远的将来==和!=将隐式生成。
- 似乎这遥远的未来不会是2017年、18年、19年,好吧,你明白我的意思了……
不可能定义默认的==,但是您可以通过==定义默认的!=,您通常应该定义自己。为此,您应执行以下操作:
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中使用的方法更好,因为它为您只需要的类定义了操作符的用法,而不是为作用域中的所有类。
您还可以从"+=",-从"-="等生成"+"…(请参阅完整列表)
- 在编写==操作符之后,我没有得到默认的!=。或者我做了,但它缺乏以东(7)。我也得自己写,一切都很好。
- 你可以玩const-ness来达到需要的结果。如果没有代码,就很难说出它出了什么问题。
- @巴里,你的回答被否决了,这已经是10多年前的事了,现在才是事实。你想让我在10年后追踪我所有的答案吗????:-))你的行为对我来说是一种贬低。
- 有一个原因,在C++ 20中,EDCOX1〔8〕被弃用:因为它不起作用,至少不是到处都是,而且肯定不是一致的。没有可靠的方法可以让sort_decreasing()编译。另一方面,Boost.Operators工作并且一直工作。
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<=> |
- 我很惊讶他们用Point作为一个订购操作的例子,因为没有合理的默认方法用x和y坐标订购两点……
- @pipe如果不关心元素的顺序,那么使用默认操作符是有意义的。例如,您可以使用std::set来确保所有点都是唯一的,而std::set只使用operator<。
C++0x有有默认函数的建议,所以可以说EDOCX1>0我们已经知道,这有助于明确这些事情。
- 我认为只有"特殊成员函数"(默认构造函数、复制构造函数、赋值运算符和析构函数)可以显式默认。他们把这个扩展到其他的运营商了吗?
- move构造函数也可以是默认的,但我认为这不适用于operator==。真可惜。
从概念上讲,定义平等并不容易。即使对于pod数据,也可以认为即使字段相同,但它是不同的对象(在不同的地址),也不一定相等。这实际上取决于运算符的用法。不幸的是,你的编译器不是通灵的,不能推断出这一点。
除此之外,默认函数是一种很好的方式来射自己的脚。您描述的默认设置基本上是为了保持与pod结构的兼容性。但是,它们确实会对开发人员忘记它们或默认实现的语义造成足够大的破坏。
- POD结构没有歧义——它们的行为应该与其他任何POD类型的行为完全相同,即值相等(而不是引用相等)。通过copy ctor从另一个创建的一个int等于它从中创建的一个;对两个int字段的struct所做的唯一合乎逻辑的事情就是以完全相同的方式工作。
- 好吧,但是如果pod结构有一个值,这个值的相关性依赖于另一个值呢?在值不相关的情况下,它不再是位对位相等。相等实际上可以在同一个对象(只有指针/引用)和某种程度上保存的对象(可能是子类)之间进行。
- 这是真的,但是C++确实有一个"值"的明确定义(它是在默认复制构造函数中复制的任何东西)。如果存在默认的相等,那么它的工作方式肯定是相同的:它只在每个字段上调用operator=。这将确保将任何pod值作为值进行比较,通过引用比较指针,并将具有重写operator=方法的对象与预期的类设计器进行比较。从概念上讲,定义平等很容易。他们不是因为其他原因才这么做的。
- @即使对于pod结构,pavelminaev也取决于它们所代表的内容是否相同。是否应将相等定义为标识、等价或其他选项。我同意你的观点,对于许多pod结构,等价(相同的数据值)是正确的答案。
- @pavelminaev:我所能想到的所有用于一般用途的等价性测试的方法都需要将每个对象与自身进行比较。不幸的是,ieee-754禁止==和!=运营商展示这种行为。
- @mgiuca:考虑到struct {double v;} a,b; a.v=0.0/0.0; b=a;,a==b应该报告什么?如果不是因为使用浮点类型的==的破坏行为,那么a.v==b.v是正确的,a==b不会造成问题。尽管如此,尽管我可以看到有一种标准方法要求对象测试等价性有很大的价值,但这种有用性将以契约要求为前提,即测试表现为等价关系,而==运算符不能处理所有类型。
- @supercat为什么您认为需要自动==运算符来满足自反性?我同意这是理想的,但是考虑到C++已经具有EDCOX1和0个不自反的运算符(如你所指出的),一个自动生成的EDCOX1×0的操作符对于一个复合类型只能和它的字段的EDCOX1×0的操作符一样好。这并不影响自动生成默认的==运算符的实用性,该运算符表示"如果根据==所有字段都相等,则我们是相等的"。
- @mgiuca:我可以看到一个通用等价关系的相当大的用处,它允许任何作为值的类型被用作字典或类似集合中的键。然而,如果没有一个有保证的自反等价关系,这样的集合就不能有效地工作。imho,最好的解决方案是定义一个所有内置类型都可以合理实现的新操作符,并定义一些与现有指针类型类似的新指针类型,除了一些类型将相等定义为引用等价,而另一些类型将链接到目标的等价操作符。
- @supercat:这实际上只是==实现遵守等价关系规则的一个理由。不幸的是,那艘船已经开航了,我不认为引进一个与==几乎完全相同的新操作员,除了在一些角落的情况下,真的很有帮助。据我所知,所有内置类型都遵循除浮点和双精度之外的等价律。当然,用户定义的类型可以定义==并违反这些规则,但是新的操作符也可以这样定义。所有这些都不会影响用户定义类型是否有自动==是有用的。
- @MGIUCA:在很多情况下,需要测试浮点数的等价性。如果想要测试fp等价性的程序员必须编写类似于bool equals(double x, double y) { return (x==y) || (x!=x) && (y!=y); }的代码,那么即使在允许直接等价性测试的硬件上,这种测试也往往效率低下。添加一种对fp类型有效的等价测试方法,但也可以由其他类型实现,这些类型承诺它表示等价关系,这似乎比仅为fp类型添加一种方法更好。
- @Supercat抱歉回复慢。是的,我并不反对对FP有一个适当的等价物会有帮助。相反,您所讨论的问题的根源不是==操作符被破坏,而是fps的相等性被破坏。对于几乎所有的意图和目的,==都表示一个等价类,因此自动将其扩展到结构是有意义的。它只是有一个罕见的边缘案件,它是打破。添加一个新的运算符只会混淆问题,几乎不能解决任何问题。(为什么有两个相等运算符?"!"
- @supercat通过类比,您可以对+运算符进行几乎相同的论证,因为它对于float是非关联的;即(x + y) + z!=x + (y + z),因为fp舍入的方式。(可以说,这是一个比==更糟糕的问题,因为对于普通数值来说是正确的。)您可能建议添加一个新的加法运算符,它适用于所有数值类型(甚至是int),几乎与+完全相同,但它是关联的(以某种方式)。但是,如果你真的没有帮助那么多人的话,你会给语言增加膨胀和混乱。
- @MGIUCA:除了边缘情况外,拥有非常相似的东西通常是非常有用的,并且错误的努力避免这些东西会导致不必要的复杂性。如果客户机代码有时需要以一种方式处理边缘案例,有时需要以另一种方式处理边缘案例,那么每种处理方式都有一个方法将消除客户机中的大量边缘案例处理代码。至于您的类比,在所有情况下都无法定义对固定大小的浮点值的操作,以产生可传递的结果(尽管一些80年代的语言有更好的语义…
- …比今天的情况更糟)因此,他们不做不可能的事也不应该是一个意外。然而,实现一种等价关系没有根本的障碍,这种等价关系将普遍适用于任何类型的可复制值。
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案例,其中两个字段是唯一的,所以一个比较永远不会被认为是真的。然而,我只需要在有效负载上进行比较——这是编译器永远无法理解或自己也无法理解的。
另外,他们不需要花很长时间来写作,是吗?!