是什么让Scala的运算符重载“好”,但C ++的“坏”?
C ++中的运算符重载被许多人认为是一件坏事(tm),并且在新语言中不会重复。 当然,这是deviseJava时特别放弃的一个特性。
现在我已经开始阅读Scala,我发现它看起来非常像运算符重载(尽pipe在技术上它没有运算符重载,因为它没有运算符,只有函数)。 然而,在C ++中,运算符重载似乎没有什么本质的区别,我记得运算符被定义为特殊函数。
所以我的问题是什么使得在Scala中定义“+”的想法比在C ++中更好?
C ++从Cinheritance了真正的蓝色运算符。这意味着6 + 4中的“+”是非常特殊的。 例如,你不能得到一个指向那个+函数的指针。
另一方面,斯卡拉没有这样的运营商。 它在定义方法名称方面具有很大的灵活性,并且对非字符号有一定的优先级。 所以技术上Scala没有运算符重载。
无论你想调用它,运算符重载本质上并不坏,即使在C ++中也是如此。 问题是坏的程序员滥用它。 但坦率地说,我认为,让程序员滥用操作符重载的能力并不会让程序员滥用所有的东西。 真正的答案是指导。 http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html
无论如何,C ++的操作符重载和Scala的灵活的命名方式(恕我直言)使得Scala不那么可滥用和更可滥用是不同的。
在C ++中,获取补丁表示法的唯一方法是使用运算符。 否则,您必须使用object.message(参数)或指针 – >消息(参数)或函数(参数1,参数2)。 所以,如果你想要一些DSL的风格来代码,那么使用操作符就有压力。
在Scala中,您可以通过发送任何消息来获得中缀符号。 “对象消息参数”是完全正确的,这意味着你不需要使用非单词符号来获得中缀符号。
C ++运算符重载仅限于C运算符。 结合限制只有操作者可能被使用的中缀向人们施加压力,试图将各种不相关的概念映射到诸如“+”和“>>”等相对较less的符号上。
Scala允许大量有效的非单词符号作为方法名称。 例如,我有一个embedded的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
:,!,?和&符号被定义为普通的方法。 在C ++中,只有&将是有效的,所以试图将这个DSL映射到C ++将需要一些已经引起非常不同的概念的符号。
当然,这也打开了斯卡拉另一种虐待。 在Scala中,如果你愿意的话,你可以命名一个方法$!&^%。
对于其他像Scala一样灵活使用非字函数和方法名的语言,请参阅Smalltalk,Scala中的每个“运算符”只是另一种方法,Haskell允许程序员灵活定义优先级和固定性function。
运算符在C ++中的重载被许多人认为是一件坏事(tm)
只有无知。 对于像C ++这样的语言来说,这是绝对必要的,而且很明显,其他语言开始采用“纯粹”的观点,一旦他们的devise者发现了它的必要性,就join了它。
运算符重载在C ++中从未被普遍认为是一个坏主意 – 只是运算符超载的滥用被认为是一个坏主意。 一种语言并不需要操作符重载,因为无论如何都可以用更详细的函数调用来模拟它们。 在Java中避免运算符重载使得Java的实现和规范变得简单一些,并迫使程序员不要滥用运算符。 关于引入操作符重载,Java社区已经有一些争论。
在Scala中运算符重载的优点和缺点与C ++相同 – 如果您恰当地使用运算符重载,则可以编写更自然的代码 – 如果您不使用运算符重载,则可以编写更多的代码。
仅供参考:运算符在C ++中没有被定义为特殊函数,它们的行为与其他函数一样 – 虽然在名称查找方面有一些不同,它们是否需要成员函数,以及它们可以通过两种方式调用: )运算符语法,以及2)运算符函数id语法。
本文 – “ C ++和Java的积极遗产 ” – 直接回答你的问题。
“C ++既有堆栈分配也有堆分配,你必须重载你的操作符来处理所有的情况,而不会造成内存泄漏,但是,Java有一个单一的存储分配机制和一个垃圾回收器,这使得操作符重载变得不重要。 ..
错误的Java(根据作者)省略了运算符重载,因为它在C ++中是复杂的,但忘记了为什么(或者没有意识到它不适用于Java)。
值得庆幸的是,Scala等更高级的语言为开发人员提供了select,同时仍然运行在同一个JVM上。
操作符重载没有任何问题。 事实上, 没有运算符重载数值types是有问题的。 (看看使用BigInteger和BigDecimal的一些Java代码。)
虽然C ++有滥用function的传统。 一个经常被引用的例子是,bitshift运算符被重载来做I / O操作。
一般来说这不是一件坏事。
像C#这样的新语言也有运算符重载。
这是操作员超载的滥用,这是一件坏事。
但是在C ++中定义的运算符重载也存在问题。 因为重载操作符只是方法调用的语法糖,所以它们的行为就像方法一样。 另一方面,普通的内置操作符不像方法那样工作。 这些不一致可能会导致问题。
在我的头顶操作员||
和&&
。
这些内置版本是捷径运营商。 这对于重载的版本并不正确,并导致了一些问题。
+ – * /全部返回与操作相同的types(在操作员提升之后)
重载的版本可以返回任何东西(这是滥用的地方,如果你的操作员开始返回一些仲裁types的用户不希望事情下山)。
运算符重载并不是你真正“需要”的东西,但是当使用Java时,如果你碰到了真正需要它的地方,它会让你想要撕掉你的指甲,这样你就可以停止打字。
你刚刚发现的代码溢出了很长时间? 是的,你将不得不重新打字,才能使用BigInteger。 没有什么比这更令人沮丧的了,不得不重新发明轮子来改变variables的types。
盖伊·斯蒂尔(Guy Steele)认为,运营商的重载应该也是用Java来做的,在他的主题演讲“Growing a language”中有一个video和一个转录,这真是一个非常棒的演讲。 你会想知道前几页他在说什么,但是如果你继续阅读,你会看到这一点,并达到启迪。 而他能够做这样一个演讲的事实也是惊人的。
与此同时,这次演讲启发了很多基础研究,可能包括斯卡拉 – 这是大家应该阅读的领域之一。
回到关键点,他的例子主要是关于数字类(比如BigInteger和一些怪异的东西),但这不是必需的。
但是,错误地使用操作符重载会导致可怕的结果,如果您尝试读取代码而不研究它使用的库,即使正确使用也会使问题复杂化。 但这是一个好主意吗? OTOH,这些图书馆不应该为操作员包含一个操作员备忘单吗?
运算符重载不是一个C ++发明 – 它来自Algol IIRC,甚至Gosling也不认为这是一个糟糕的主意。
我相信每一个答案都错过了这个。 在C ++中,你可以重载所有你想要的操作符,但是你不能影响它们被评估的优先级。 Scala没有这个问题,IIRC。
至于这是一个坏主意,除了优先权问题之外,人们对于操作人员提出了非常愚蠢的含义,而且很less提供可读性。 斯卡拉图书馆对此特别不利,每次你必须背诵一些愚蠢的符号,图书馆维护人员在沙子上埋头说:“你只需要学习一次”。 太棒了,现在我需要学习一些“聪明的”作者的神秘语法*我所关心的库的数量。 如果有一个总是提供一个文字版本的运营商的常规,那也不会这么糟糕。
在C ++中唯一已知错误的是缺less将[] =作为独立运算符的能力。 这可能很难在C ++编译器中实现,这可能不是一个明显的原因,但值得一试。
正如其他答案指出的那样; 操作符重载本身不一定是坏的。 什么是坏的时候,使得代码不明显的方式使用。 一般来说,在使用它们的时候,你需要让它们做最不令人吃惊的事情(让操作符+分割会为理性类的使用带来麻烦),或者像Scott Meyers所说的那样:
客户已经知道像int这样的types是如何运作的,所以你应该努力让自己的types在合理的时候以相同的方式行事。 (来自Effective C ++第3版第18项)
现在有些人把运营商的重载放到了极致,比如boost :: spirit 。 在这个级别上,你不知道它是如何实现的,但它使得一个有趣的语法来获得你想要的结果。 我不确定这是好还是不好。 这似乎很好,但我没有用过它。
我从来没有见过一篇文章声称C ++的操作符重载是不好的。
用户可定义的操作员可以为语言用户提供更高级别的expression能力和可用性。
然而,在C ++中,运算符重载似乎没有什么本质的区别,我记得运算符被定义为特殊函数。
AFAIK,与“普通”成员函数相比,运算符函数没有什么特别之处。 当然,你只有一套可以重载的操作符,但是这并不是特别的。