是什么让Scala的运营商重载"好",但是C++的"糟糕"?

ska*_*man 153 c++ scala operator-overloading

C++中的运算符重载被许多人认为是坏事(tm),并且在新语言中不会重复错误.当然,这是设计Java时专门删除的一个功能.

现在我开始阅读Scala,我发现它看起来非常像运算符重载(虽然从技术上来说它没有运算符重载,因为它没有运算符,只有函数).但是,它似乎与C++中的运算符重载没有本质上的区别,因为我记得运算符被定义为特殊函数.

所以我的问题是什么使得在Scala中定义"+"的想法比在C++中更好?

Jam*_*Iry 239

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

另一方面,Scala没有这样的运算符.它在定义方法名称和非字符号的一些内置优先级方面具有很大的灵活性.所以从技术上讲,Scala没有运算符重载.

无论你想要什么,运算符重载本身并不坏,即使在C++中也是如此.问题在于糟糕的程序员滥用它.但坦率地说,我认为剥夺程序员滥用运算符重载的能力并不能解决程序员可能滥用的所有问题.真正的答案是指导. http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

尽管如此,C++的运算符重载和Scala的灵活方法命名之间存在差异,恕我直言,这使得Scala不那么容易滥用和更容易被滥用.

在C++中,获取in-fix表示法的唯一方法是使用运算符.否则,您必须使用object.message(参数)或指针 - > messsage(参数)或函数(argument1,argument2).因此,如果您希望代码具有某种DSLish风格,那么使用运算符会有压力.

在Scala中,您可以通过任何消息发送获取中缀表示法."对象消息参数"完全没问题,这意味着您不需要使用非单词符号来获取中缀表示法.

C++运算符重载仅限于C运算符.结合只有操作符可以使用的限制,这会给人们施加压力,试图将各种不相关的概念映射到相对较少的符号,如"+"和">>"

Scala允许使用大量有效的非单词符号作为方法名称.例如,我有一个嵌入式Prolog-ish 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
Run Code Online (Sandbox Code Playgroud)

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

当然,这也使Scala遭受了另一种虐待.在Scala中,如果需要,可以将方法命名为$!&^%.

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


小智 66

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

只有无知.在像C++这样的语言中绝对需要它,并且值得注意的是,其他开始采用"纯粹主义"观点的语言,一旦他们的设计师发现它有多么必要,就添加了它.

  • 我实际上同意尼尔.如果你想呈现的变量/常量/对象/实例作为代数实体......,让人们了解他们在数学的方式交互操作符重载是至关重要的 - 这应该是规划如何工作恕我直言. (30认同)
  • +1,C++中的运算符重载是好的.例如,它使矢量数学更加清晰.与许多C++功能一样,您应该谨慎使用电源. (15认同)
  • "那种方式就是疯狂" - 更糟糕的是,那种方式就是std :: vector <bool>! (8认同)
  • @Kristo因为C++使用必须分配和复制的值.必须对此进行控制,因此您必须至少能够为给定类型指定赋值运算符. (7认同)
  • @Kristo:因为C++的一个目的是允许用户自定义类型做到这一点内建类型做(尽管他们在某些情况下区别对待,如隐式转换)的一切.如果你想实现一个27位整数,那么你可以使用它就像使用int一样.如果没有操作符重载,它不会有可能使用的UDT具有相同的语法内建类型,因此所产生的语言就不会"像C++"在这个意义上. (7认同)
  • 尼尔的例子是一个重要的例子:考虑"int a(0); a = a + 1;".正如C++所说,我可以做到,"MyInt27 a(0); a = a + 1;".在没有运算符重载的C++中,必须是"MyInt27 a(0); a.set(a.plus(1));".不完全是替代品.因此,根据用户的要求,编写一个可以使用int或MyInt27的模板是不可能的. (4认同)
  • 再举一个例子,我已经习惯了`<<`如何用于流.您可以使用方法调用替换该运算符,但是我发现`cout <<"Hello"<< name <<"!";`比`cout.print("Hello")更整洁.print(name).print( ) "!";` (3认同)
  • 我非常赞同这个答案.我更喜欢C++而不是Java的原因之一是因为*允许*重载运算符.它只是在OO环境中提供了更大的灵活性.我不是编程的新手,而且我不是一个白痴......我不需要我的编程语言牵着我的手,并迫使我以某种方式编码.每次输入长功能名称*我想做点什么不是我的"有趣"的想法. (3认同)
  • 但我认为Java必须删除运算符重载; 意识到他们为了仿制药而搞砸了; 发明自动装箱来解决问题; 清楚地表明C++正在做正确的事情.我知道对运算符重载的一个明确的论点是,如果你没有它,那么一组"简单"表达式只是"做明显的事情".除了a/b仍然可以将外线调用到64位或浮动仿真库之外,我个人认为虽然很清楚,但这种说法是错误的.;-) (2认同)

Fai*_*ali 41

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

Scala中运算符重载的优点和缺点与C++中的相同 - 如果适当地使用运算符重载,则可以编写更自然的代码 - 如果不这样,可以使用更加神秘,混淆的代码.

FYI:运营商不是在C++定义为特殊功能,它们的行为就像任何其他的功能 - 虽然在名称查找一些差异,是否他们需要的是成员函数,而事实上,他们可以通过两种方式调用:1 )运算符语法,以及2)operator-function-id语法.


jma*_*g2k 18

本文 - " C++和Java的积极遗产 " - 直接回答您的问题.

"C++既有堆栈分配又有堆分配,你必须重载你的操作符来处理所有情况而不会导致内存泄漏.确实很困难.但是,Java有一个存储分配机制和一个垃圾收集器,这使得运营商重载变得微不足道". ..

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

值得庆幸的是,像Scala这样的高级语言为开发人员提供了选项,同时仍然在同一个JVM上运行.

  • Eckel是我见过的唯一一个因为C++的复杂性导致运算符重载被Java抛弃的想法,他没有说明他的来源是什么.我会打折它.我所说的所有其他消息来源都说由于潜在的滥用而被抛弃了.见http://www.gotw.ca/publications/c_family_interview.htm和http://www.newt.com/wohler/articles/james-gosling-ramblings-1.html.只需在页面中搜索"运算符重载". (14认同)

dan*_*n04 9

运算符重载没有错.事实上,还有一些错误具有运算符重载数字类型.(看一下使用BigInteger和BigDecimal的一些Java代码.)

但是,C++具有滥用该功能的传统.一个经常被引用的例子是比特移位运算符过载以进行I/O.

  • @peenut:但他们最初使用的是位移."标准库"以完全与原始定义混淆的方式使用运算符. (7认同)

Mar*_*ork 8

一般来说,这不是一件坏事.
C#等新语言也有运算符重载.

滥用运算符重载是一件坏事.

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

我的头顶操作员||&&.
这些内置版本是快捷操作符.对于重载版本而言,情况并非如此,并且已导致一些问题.

事实上+ - */all返回它们运行的​​相同类型(在运算符提升之后)
重载版本可以返回任何内容(这是滥用设置的位置,如果您的运算符开始返回某些仲裁器类型,则用户不期望事情走下山).


小智 7

操作符重载不是你真正"经常"需要的东西,但是当你使用Java时,如果你真的需要它,它会让你想要撕掉你的指甲,这样你就有借口停止打字.

你刚刚发现的代码溢出了很长时间?是的,你将不得不重新输入整个批次以使其适用于BigInteger.没有什么比仅仅为了改变变量的类型而重新发明轮子更令人沮丧的了.


Bla*_*ade 6

Guy Steele认为运营商超载也应该是Java,在他的主题演讲"发展一种语言" - 有一个视频和它的转录,这真是一个惊人的演讲.你会想知道他在前几页会谈到什么,但如果你继续阅读,你会看到重点并获得启示.事实上他可以做这样的演讲也很神奇.

与此同时,这次演讲激发了许多基础研究,可能包括Scala--这是每个人都应该阅读以在该领域工作的论文之一.

回到这一点,他的例子主要是关于数字类(比如BigInteger,以及一些更奇怪的东西),但这并不重要.

但是,错误地使用运算符重载可能会导致可怕的结果,如果您尝试读取代码而不仔细研究它使用的库,那么即使正确使用也会使问题复杂化.但这是个好主意吗?OTOH,这些图书馆是否应该尝试为其运营商提供操作员备忘单?