是什么使Scala的运算符重载“好”,而C ++的“坏”?


155

许多人认为C ++中的运算符重载是一个坏东西(tm),并且是一个错误,不要在较新的语言中重复出现。当然,这是设计Java时专门删除的一项功能。

既然我已经开始阅读Scala,我发现它看起来非常类似于运算符重载(尽管从技术上讲,它没有运算符重载,因为它没有运算符,只有函数)。但是,它似乎与C ++中的运算符重载在质量上没有什么不同,在C ++中,我记得运算符被定义为特殊函数。

因此,我的问题是什么使Scala中定义“ +”的想法比C ++中的想法更好?


27
所有程序员之间的普遍共识都没有定义C ++和Scala。我认为有些人对C ++怀有抱怨,而有些人不对Scala怀有抱怨,这之间没有矛盾。
史蒂夫·杰索普

16
C ++中的运算符重载没有什么坏处。
小狗

5
这并不是什么新鲜事物,但是当操作员重载和其他“高级”功能受到质疑时,我捍卫C ++的方式很简单:C ++赋予了我们所有使用/滥用它的能力。我一直很喜欢我们被认为具有能力和自治能力,并且不需要像这样为我们做出的决定。
艾略特(Elliott)2012年

Scala的设计像c ++之后的几十年。事实证明,背后的人在编程语言方面是超级专家。两者本身都没有什么不好的,如果您再坚持使用c ++或Scala 100年,那么很明显,两者可能都是不好的!偏见显然是我们的本性,但我们可以与之抗衡,只要看看技术的历史,一切都会过时的。
Nader Ghanbari

Answers:


242

C ++从C继承了真正的蓝色运算符。我的意思是6 + 4中的“ +”非常特殊。例如,您无法获得指向该+函数的指针。

另一方面,Scala没有这种方式的运算符。它在定义方法名称方面具有极大的灵活性,并且为非单词符号提供了一些内置的优先级。因此,从技术上讲,Scala没有运算符重载。

不管您要调用它什么,即使在C ++中,运算符重载也不是很坏。问题是当不良的程序员滥用它时。但坦率地说,我认为,剥夺程序员滥用操作员重载的能力并不能解决所有程序员可能滥用的问题。真正的答案是指导。 http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

尽管如此,C ++的运算符重载与Scala的灵活方法命名之间存在差异,恕我直言,这使Scala的可滥用性降低了。

在C ++中,获取固定中标记的唯一方法是使用运算符。否则,必须使用object.message(argument)或pointer-> messsage(argument)或function(argument1,arguments2)。因此,如果您希望代码具有某种DSLish样式,则使用操作符会有压力。

在Scala中,您可以在发送任何消息时获得中缀符号。“对象消息参数”是完全可以的,这意味着您无需使用非单词符号即可获得中缀表示法。

C ++运算符重载实际上仅限于C运算符。加上只能使用运算符的限制,这给人们施加了压力,他们试图将各种不相关的概念映射到相对较少的符号上,例如“ +”和“ >>”

Scala允许使用大量有效的非单词符号作为方法名称。例如,我有一个嵌入式的Prolog式DSL,您可以在其中编写

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent

mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female

mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

:-,!,?和&符号定义为普通方法。仅在C ++中,&将是有效的,因此尝试将此DSL映射到C ++将需要一些已经引起了非常不同概念的符号。

当然,这也使Scala遭受了另一种滥用。在Scala中,可以根据需要命名方法$!&^%。

对于像Scala这样可以灵活使用非单词函数和方法名称的其他语言,请参阅Smalltalk,其中像Scala一样,每个“运算符”都只是另一种方法,而Haskell允许程序员定义灵活命名的优先级和固定性。功能。


最后我检查了一下3.operator +(5)是否有效。我真的很惊讶&(3.operator +)没有。
约书亚

例如,您可以在c ++中执行assert(female(“ jane”))。这一点根本不会引起混淆-再次回到james-iry帖子上,说不是operator +是一件坏事,但愚蠢的程序员则是。
2010年

1
@Joshua int main() {return (3).operator+(5);}导致error: request for member ‘operator+’ in ‘3’, which is of non-class type ‘int’
zildjohn01 2010年

那是一堆傲慢的废话:“即使在C ++中,运算符重载也不是天生的坏问题。问题是当不良的程序员滥用它时。” 如果某些东西很容易被滥用,而使用它却没有什么好处,那么总体结果是,下一个维护您的代码的人将在解密代码的怪异部分时失去工作效率。否则:内容翔实且写得很好的答案。
Jukka Dahlbom 2014年

@JukkaDahlbom智能指针的存在本身就使收益大增。然后便有了lambda,用户定义的数字类型,区间类型...
Alexey Romanov

66

许多人认为C ++中的运算符重载是一件坏事(tm)

只由无知。在像C ++这样的语言中,它是绝对必需的,并且值得注意的是,其他开始以“纯粹”的观点开始使用的语言,一旦设计人员发现了必要性,便添加了它。


30
我实际上同意尼尔。如果您想将变量/常数/对象/实例表示为代数实体...并且让人们以数学方式理解它们的相互作用,则运算符重载是必不可少的-这应该是IMHO编程的工作方式。
马萨2009年

16
+ 1,C ++中的运算符重载是好的。例如,它使向量数学更整洁。与许多C ++功能一样,您应该谨慎地使用它。
约翰·史密斯,2009年

7
@Kristo因为C ++使用必须分配和复制的值。必须对此进行控制,因此您必须至少能够为给定类型指定赋值运算符。

7
@Kristo:因为C ++的目的之一是允许用户定义的类型执行内置类型所做的所有事情(尽管在某些情况下(例如隐式转换)对它们的处理有所不同)。如果要实现27位整数,则可以实现,并且使用它就像使用int一样。如果没有运算符重载,则无法使用与内置类型具有相同语法的UDT,因此在这种意义上,结果语言将不会像“ C ++”。
史蒂夫·杰索普

8
“这就是疯狂” –更糟糕的是,这就是std :: vector <bool>!
史蒂夫·杰索普

42

在C ++中,从来没有普遍认为运算符重载是一个坏主意-只是滥用运算符重载被认为是一个坏主意。一个人实际上并不需要某种语言中的运算符重载,因为无论如何它们都可以用更多冗长的函数调用来模拟。避免Java中的运算符重载使Java的实现和规范更加简单,并且迫使程序员不要滥用运算符。Java社区中有一些关于引入运算符重载的争论。

Scala中的运算符重载的优缺点与C ++相同-如果适当地使用运算符重载,则可以编写更自然的代码-如果不使用,则可以编写更模糊,混淆的代码。

仅供参考:运算符未在C ++中定义为特殊函数,它们的行为与其他函数相同-尽管名称查找存在一些差异,是否需要成为成员函数,以及可以通过两种方式调用它们: ),运算符语法和2)运算符功能ID语法。


“一个人实际上并不需要某种语言中的运算符重载,因为无论如何它们都可以用更多冗长的函数调用来模拟。” 在这种逻辑下,甚至没有人真正需要运算符。为什么不只是使用add(2, multiply(5, 3))
Joe Z.

这是匹配常用符号的一种情况。考虑一下数学家和物理学家,他们可以理解和使用C ++库,该库可以更轻松地提供运算符的重载。他们宁愿专注于方程而不是编程语言。
Phil Wright

19

本文-“ C ++和Java的积极遗产 ”-直接回答您的问题。

“ C ++同时具有堆栈分配和堆分配,并且您必须使运算符重载以处理所有情况,而不会导致内存泄漏。确实很难。但是,Java具有单一的存储分配机制和垃圾收集器,这使得运算符重载变得微不足道”。 ..

Java错误地(根据作者)省略了运算符重载,因为它在C ++中很复杂,但是却忘记了原因(或没有意识到它不适用于Java)。

幸运的是,像Scala这样的高级语言为开发人员提供了选择,同时它们仍在同一JVM上运行。


14
Eckel是我见过的唯一消息来源,因为由于C ++的复杂性,运算符重载已被Java抛弃了,他没有说他的消息来源是什么。我会打折。我说过的所有其他消息都说,它是由于潜在的滥用而被抛弃的。参见gotw.ca/publications/c_family_interview.htmnewt.com/wohler/articles/james-gosling-ramblings-1.html。只需在页面上搜索“操作员超载”即可。
James Iry

9

运算符重载没有任何问题。实际上,具有运算符重载数字类型。(看一些使用BigInteger和BigDecimal的Java代码。)

但是,C ++具有滥用功能的传统。一个经常被引用的例子是,移位操作符被超载以进行I / O。


<<和>>运算符在视觉上指示了传输方式,它们的目的是进行I / O操作,不是滥用,而是来自标准库和实际操作。只要看“ cin >>某物”,那会去哪里?从cin到某种东西,很明显。
peenut 2012年

7
@peenut:但是它们最初的用途是移位。“标准库”以完全与原始定义混淆的方式使用运算符。
Joe Z.

1
我确定我已经读过某个地方,Bjarne Stroustrup(C ++的创建者)在C ++ 的早期就尝试使用=代替,<<并且>>遇到了问题,因为它没有正确的运算符优先级(即,它寻找左或右的第一个参数)。因此,他的双手在他可以使用的东西上受了一些束缚。
Phil Wright

8

一般来说,这不是一件坏事。
诸如C#之类的新语言也具有运算符重载。

滥用操作员重载是一件坏事。

但是,C ++中定义的运算符重载也存在问题。因为重载运算符只是方法调用的语法糖,所以它们的行为就像方法一样。另一方面,普通的内置运算符的行为不像方法。这些不一致可能会引起问题。

在我的头顶上,运营商||&&
这些的内置版本是捷径运算符。这对于重载版本不是正确的,并且已经引起了一些问题。

+-* /全部返回与其操作相同的类型(在操作员升级之后)的事实
过载版本可以返回任何内容(这是滥用的地方,如果您的操作员开始返回用户所期望的某些仲裁器类型,事情就下山了)。


8

运算符重载并不是您真正经常需要的东西,但是在使用Java时,如果碰到了真正需要它的地方,它将使您想要撕掉指甲,只是您有借口停止打字。

您刚刚发现的代码会长时间溢出?是的,您必须重新输入全部内容才能与BigInteger一起使用。没有什么比不得不重新发明轮子来更改变量类型更令人沮丧的了。


6

盖伊·斯蒂尔(Guy Steele)认为,运算符重载也应该用Java编写,他在主题演讲“成长一种语言”中-有一个视频和它的转录,这确实是一个了不起的演讲。您会想知道他在前几页中在谈论什么,但是如果您继续阅读,您将了解重点并获得启发。而且他完全可以发表这样的演讲,这一事实也令人惊讶。

同时,本次演讲激发了许多基础研究的灵感,其中可能包括Scala-这是每个人都应该阅读以从事该领域工作的那些论文之一。

回想一下,他的示例主要涉及数字类(例如BigInteger和一些怪异的东西),但这不是必需的。

但是,如果您不花一点时间就尝试阅读代码,那么滥用操作符重载会导致可怕的结果,甚至正确使用也会使事情变得复杂。但这是个好主意吗?OTOH,这样的库难道不应该为他们的操作员包括操作员备忘单吗?


4

我相信每个答案都错过了这一点。在C ++中,您可以重载所有想要的运算符,但不能影响对其进行求值的优先级。Scala没有这个问题,IIRC。

至于这是一个坏主意,除了优先级问题外,人们还给操作员带来了真正愚蠢的含义,而且它很少有助于可读性。对于每次都要记住的愚蠢符号来说,Scala库特别糟糕,库维护人员将头埋在沙子里说:“您只需要学习一次”。太好了,现在我需要学习一些“聪明的”作者的神秘语法*我想使用的库数。如果有一个约定,总是提供识字版本的运算符,那并不会太糟糕。


1
Scala也具有固定的运算符优先级,不是吗?
skaffman

我相信有,但是要讨价还价。更重要的是,Scala的运营商周期更短。+,-,*是方法,不是IIRC的运算符。这就是为什么2 + 3 * 2不是8,而是10的原因
。– Saem

7
Scala具有基于符号第一个字符的优先级系统。scala> 2 + 3 * 2 res0:Int = 8
James Iry

3

运算符重载不是C ++的发明-它来自Algol IIRC,甚至Gosling都不认为这是一个坏主意。


当然可以,但是它在C ++的化身中赢得了普遍的声誉。
skaffman

5
“无耻的一般气氛”是什么意思?我认识的大多数人都使用支持运算符重载的语言(C ++,C#),而且我从未听说过任何抱怨。
Nemanja Trifunovic,2009年

我是从过去对ANSI C ++的长期经验中讲的,我当然记得对它们的普遍不满。也许情况随着ANSI C ++的改善而有所改善,或者人们刚刚学会了如何不滥用它。
skaffman

1
我可以肯定地说,自八十年代(80年代中期)以来一直使用C ++的人,我可以向您保证,引入ISO标准不会影响人们对运算符重载的偏见。

3

在C ++中唯一已知的错误是缺少将[] =作为单独的运算符进行重载的能力。这可能很难在C ++编译器中实现,原因可能不是显而易见的原因,但值得这样做。


2

正如其他答案所指出的;操作符重载本身并不一定是坏事。以某种方式使结果代码变得不明显时,有什么不好呢?通常,在使用它们时,您需要使它们执行最不令人惊讶的事情(使用operator +做除法会对合理类的使用造成麻烦)或如Scott Meyers所说:

客户已经知道int等类型的行为,因此在合理的情况下,您应努力使您的类型以相同的方式运行... 如有疑问,请按int的方法进行操作。(摘自有效的C ++ 3rd Edition项目18)

现在,有些人通过boost :: spirit之类的方法将运算符重载达到了极限。在这个级别上,您不知道它是如何实现的,但是它提供了一种有趣的语法来完成您想要的工作。我不确定这是好是坏。看起来不错,但是我还没用过。


我在这里不是在支持或反对运算符重载,我不是在寻找可以证明其合理性的人。
skaffman 2009年

Sprint远没有我遇到过的最糟糕的例子-您应该了解RogueWave数据库库的最新知识!

我同意Spirit滥用操作员,但是我真的想不出更好的方法。
Zifre

1
我不认为运营商会滥用精神,但它正在推动这种精神。我同意,实际上没有其他方法可以做到。它基本上是用C ++的语法创建一个DSL。与C ++的设计相去甚远。是的,还有更糟糕的例子:)通常,我会在适当的地方使用它们。通常,只有流运算符可简化调试\日志记录。甚至只有糖可以转发到类中实现的方法。
马特·普赖斯

1
这是一个味觉问题;但是使用功能语言的解析器组合器库以类似于 Spirit 的方式重载运算符,没有人反对。有许多更好的技术原因-Google的“嵌入式领域特定语言”可以找到大量从一般角度进行解释的论文,而Google的“ scala解析器组合器”可以作为实际示例。的确,在函数式语言中,生成的语法通常更好-例如,您不需要更改>>的含义来连接解析器。
Blaisorblade

2

我从未见过一篇文章声称C ++的运算符重载是不好的。

用户可定义的运算符使该语言的用户可以更轻松地实现更高的表达性和可用性。


1

但是,它似乎与C ++中的运算符重载在质量上没有什么不同,据我所知,在C ++中,运算符被定义为特殊函数。

AFAIK,与“普通”成员函数相比,操作员函数没有什么特别的。当然,您只有一组可以重载的运算符,但这并没有使它们非常特殊。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.