我非常喜欢让编译器为你做尽可能多的工作.在编写一个简单的类时,编译器可以为"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 { }
有这么好的理由吗?为什么执行逐个成员比较会成为问题?显然,如果类分配内存然后你要小心,但对于一个简单的类肯定编译器可以为你做这个?
该论点认为,如果编译器可以提供默认的复制构造函数,那么它应该能够提供类似的默认值operator==()
,具有一定的意义.我认为决定不为此运算符提供编译器生成的默认值的原因可以通过Stroustrup在"C++的设计和演化"(第11.4.1节 - 复制控制)中对默认复制构造函数的说法进行猜测. :
我个人认为不幸的是,复制操作是默认定义的,我禁止复制我的许多类的对象.但是,C++从C继承了它的默认赋值和复制构造函数,并且它们经常被使用.
因此operator==()
,问题应该是"为什么C++没有默认赋值和复制构造函数?",答案是Stroustrup不情愿地将这些项目包含在内,以便向后兼容C(可能是大多数C++瑕疵的原因,也可能是C++流行的主要原因).
出于我自己的目的,在我的IDE中,我用于新类的片段包含私有赋值运算符和复制构造函数的声明,这样当我生成一个新类时,我没有得到默认赋值和复制操作 - 我必须显式删除声明private:
如果我希望编译器能够为我生成它们,那么该部分的操作.
更新2:不幸的是,这个提议没有进入C++ 17,所以现在这方面没有任何改变.
更新:它具有很高的几率获得投入C++ 17提案的当前版本是在这里.
最近有一个关于明确默认的比较运算符的提议(N4126),它得到了标准委员会非常积极的反馈,所以希望我们能在C++ 17中以某种形式看到它.
简而言之,建议的语法是:
struct foo { std::string str; int n; }; assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed
或者operator==
以私有字段的类的形式:
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!
当然,这个提案最终被接受的时候可能会改变这一切.
编译器不知道您是想要指针比较还是深度(内部)比较.
只是不实现它并让程序员自己这样做更安全.然后他们可以做出他们喜欢的所有假设.
恕我直言,没有"好"的理由.有这么多人同意这个设计决定的原因是因为他们没有学会掌握基于价值的语义的力量.人们需要编写大量自定义复制构造函数,比较运算符和析构函数,因为它们在实现中使用原始指针.
使用适当的智能指针(如std :: shared_ptr)时,默认的复制构造函数通常很好,假设的默认比较运算符的明显实现也一样好.
答案是C++没有做= =因为C没有,这就是为什么C在第一个地方只提供default =但是没有==.C希望保持简单:C实现= memcpy; 但是,由于填充,memcmp无法实现==.因为padding没有初始化,所以memcmp说它们是不同的,即使它们是相同的.空类存在同样的问题:memcmp表示它们不同,因为空类的大小不为零.从上面可以看出,实现==比在C中实现=更复杂.关于此的一些代码示例.如果我错了,你会得到更正.
在这段视频中,STL的创建者Alex Stepanov在大约13:00解决了这个问题.总而言之,他观察了C++的演变,他认为:
不幸的是,==和!=没有被隐含声明(Bjarne同意他的意见).一个正确的语言应该为你准备好这些东西(他进一步建议你不应该定义一个!=打破==的语义)
这种情况的原因在于其根源(如同许多C++问题)在C中.赋值运算符是通过逐位赋值隐式定义的,但这对于==不起作用.Bjarne Stroustrup 在本文中可以找到更详细的解释.
在后续问题中为什么当时不是成员比较使用的成员他说了一个惊人的事情:C是一种本土语言,为Ritchie实施这些东西的人告诉他,他发现这很难实现!
然后他说,在(遥远的)未来,==和!=将被隐式生成.
无法定义默认值==
,但您可以定义默认值,您通常应该!=
通过==
它来定义自己.为此你应该做以下事情:
#includeusing 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 ::运算符.
boost中使用的方法更好,因为它为您只需要的类定义运算符的用法,而不是范围内的所有类.
您还可以从"+ ="生成"+", - 从" - ="等生成...(请参阅此处的完整列表)
C++ 20提供了一种轻松实现默认比较运算符的方法.
来自cppreference.com的示例:
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::sets; // ok s.insert(pt1); // ok if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>
C++ 0x 有一个默认函数的提议,所以你可以说default operator==;
我们已经知道它有助于使这些事情变得明确.
从概念上讲,定义平等并不容易.即使对于POD数据,人们也可以争辩说,即使字段相同,但它是不同的对象(在不同的地址),它也不一定相等.这实际上取决于运营商的使用情况.不幸的是,你的编译器不是通灵的,也无法推断.
除此之外,默认功能是拍摄自己的好方法.您描述的默认值基本上是为了保持与POD结构的兼容性.然而,它们确实会导致开发人员忘记它们或者默认实现的语义.