我非常喜欢让编译器为你做尽可能多的工作 . 在编写一个简单的类时,编译器可以为“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 回答
编译器不知道您是想要指针比较还是深度(内部)比较 .
只是不实现它并让程序员自己这样做更安全 . 然后他们可以做出他们喜欢的所有假设 .
该论点认为,如果编译器可以提供默认的复制构造函数,那么它应该能够提供类似的默认值
operator==()
具有一定的意义 . 我认为决定不为此运算符提供编译器生成的默认值的原因可以通过Stroustrup在"The Design and Evolution of C++"中对默认复制构造函数的说法进行猜测(第11.4.1节 - 复制控制):因此,而不是“为什么C没有默认
operator==()
?", the question should have been "为什么C有一个默认的赋值和复制构造函数?”,答案是Stroustrup不情愿地包含那些项目以向后兼容C(可能是因为大部分C 's warts, but also probably the primary reason for C++'的人气) .出于我自己的目的,在我的IDE中,我用于新类的片段包含私有赋值运算符和复制构造函数的声明,这样当我生成一个新类时,我没有得到默认的赋值和复制操作 - 我必须明确地删除声明如果我希望编译器能够为我生成它们,那么来自
private:
部分的那些操作 .更新2:不幸的是这个提议didn't make it to C++17,所以现在这方面的语言没有任何改变 .
更新:该提案的当前版本很有可能被投票到C 17是here .
有一个recent proposal (N4126)明确默认的比较运算符,它得到了标准委员会非常积极的反馈,所以希望我们能在C 17中以某种形式看到它 .
简而言之,建议的语法是:
或者以
friend
形式用于具有私有字段的类:或者甚至是简短的形式:
当然,这个提案最终被接受的时候可能会改变这一切 .
恕我直言,没有“好”的理由 . 有这么多人同意这个设计决定的原因是因为他们没有学会掌握基于 Value 的语义的力量 . 人们需要编写大量自定义复制构造函数,比较运算符和析构函数,因为它们在实现中使用原始指针 .
当使用适当的智能指针(如std :: shared_ptr)时,默认的复制构造函数通常很好,假设的默认比较运算符的明显实现也一样好 .
它's answered C++ didn' t = =因为C没有,这就是为什么C在第一个地方只提供default =但是没有== . C希望保持简单:C实现=通过memcpy;但是,由于填充,memcmp无法实现== . 因为padding没有初始化,所以memcmp说它们是不同的,即使它们是相同的 . 空类存在同样的问题:memcmp表示它们不同,因为空类的大小不为零 . 从上面可以看出,实现==比在C中实现=更复杂 . 有些代码example关于此 . 如果我错了,你会得到更正 .
在这个video Alex Stepanov中,STL的创建者在大约13:00解决了这个问题 . 总而言之,看过C的演变他认为:
不幸的是 == and != 没有被隐含地宣布(Bjarne同意他的意见) . 一个正确的语言应该为你准备好这些东西(他进一步建议你不应该定义一个打破 == 语义的 != )
这种情况的原因在C中有其根源(如同很多C问题) . 在那里,赋值运算符是通过逐位赋值隐式定义的,但这对 == 不起作用 . 更详细的解释可以在Bjarne Stroustrup的article中找到 .
在后续问题 Why then wasn't a member by member comparison used 中他说 amazing thing :C是一种本土语言,为Ritchie实施这些东西的人告诉他,他发现这很难实现!
然后他说,在(遥远的)未来 == 和 != 将被隐含地生成 .
无法定义默认
==
,但您可以通过==
定义默认!=
,您通常应该定义它们 . 为此你应该做以下事情:有关详细信息,请参阅http://www.cplusplus.com/reference/std/utility/rel_ops/ .
另外,如果定义
operator<
,则在使用std::rel_ops
时可以从中推导出<=,>,> =的运算符 .但是当你使用
std::rel_ops
时应该小心,因为可以推导出比较运算符用于你不期望的类型 .从基本推导出相关运算符的更优选方法是使用boost::operators .
boost中使用的方法更好,因为它为您只想要的类定义运算符的用法,而不是范围内的所有类 .
您也可以从"+="生成"+", - 从"-="等...(参见完整列表here)
C 0x有一个默认函数的提议,所以你可以说
default operator==;
我们已经知道它有助于使这些事情变得明确 .从概念上讲,定义平等并不容易 . 即使对于POD数据,也可以争辩说即使字段相同,但它是不同的对象(在不同的地址),它也不一定相等 . 这实际上取决于运营商的使用情况 . 不幸的是,你的编译器不是通灵的,也无法推断 .
除此之外,默认功能是拍摄自己的好方法 . 您描述的默认值基本上是为了保持与POD结构的兼容性 . 然而,它们确实会导致开发人员忘记它们或默认实现的语义 .
C 20提供了一种轻松实现默认比较运算符的方法 .
cppreference.com中的示例:
我同意,对于POD类型类,编译器可以为您完成 . 但是你可能认为编译器可能会出错 . 所以最好让程序员去做 .
我确实有一个POD案例,其中两个字段是唯一的 - 所以比较永远不会被认为是真的 . 然而,我所需要的比较只是在有效载荷上进行比较 - 编译器永远无法理解或者可能无法理解它本身 .
除此之外 - 他们不需要很长时间才能写出来吗?!
它在功能上可能不是问题,但就性能而言,默认的逐个成员比较可能比默认的成员分配/复制更不理想 . 与赋值顺序不同,比较顺序会影响性能,因为第一个不相等的成员意味着可以跳过其余部分 . 因此,如果有一些成员通常是相同的,那么你想最后比较它们,并且编译器不知道哪些成员更可能是相等的 .
考虑这个例子,其中
verboseDescription
是从相对较小的一组可能的天气描述中选择的长字符串 .(当然,如果编译器认识到它们没有副作用,则有权忽略比较的顺序,但可能它仍然会从源代码中获取它没有更好的信息 . )
默认的比较运算符在正确的时间内是正确的;我希望它们会成为问题的根源,而不是有用的东西 .
此外,您提到的默认方法通常是不受欢迎的 . 看到像这样的代码摆脱默认的复制构造函数和operator =是很常见的:
在很多代码中,通常会看到注释“default copy constructor and operator = OK”,表明它们已被删除或明确定义并不是错误 .