C++ 中的运算符重载是吗? 带来的麻烦比其价值还多吗?
根据我教授 C++ 的经验,运算符重载是最令学生痛苦的主题之一。 甚至在 stackoverflow 上查看问题:例如,将 + 运算符设为外部运算符还是成员运算符? 如何处理对称性等等,看起来很麻烦。
当我从 C++ 迁移到 Java 时,我担心我会错过这种能力,但除了 [] 或 () 等运算符之外,我并没有真正感觉到需要重载运算符。 事实上,我觉得没有它们的程序更具可读性。
注意:我将其作为社区维基。 我们来讨论一下这个问题。 我想听听意见。
In my experience teaching C++, operator overloading is one of those topics that causes the most grief to students. Even looking at questions here at stackoverflow: for example, make the + operator external or a member? How to handle symmetry, etc., it seems like it's a lot of trouble.
When I moved from C++ to Java, I was worried I would miss that ability, but with the exception of operators like [] or (), I haven't really ever felt the need to overload operators. In fact, I feel programs without them are more readable.
Note: I put this as a community wiki. Let's discuss this. I want to hear opinions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
重载的运算符就像香料一样。 一点点可以让事情变得更好; 太多会让人觉得难吃。
Overloaded operators are like spice. A little can make something better; too much can make it unpalatable.
每个 C++ 程序员都应该了解的一些重载示例,即使他们不赞成:
Some examples of overloading that every C++ programmer should know about, even if they don't approve:
尽管抱怨操作员过载很容易,但只要他们不以令人惊讶的方式行事,我真的不认为有什么问题。 是的,那里有一些不好的例子(即使是在 stl 中)。 例如,请参见 auto_ptr 的赋值运算符。 重载一些运算符,例如
&&
、||
和,
几乎总是不好的。 但在大多数情况下,让运营商按照他们宣传的去做,并没有什么真正的问题。重载
operator+
来做一些奇怪的事情是不好的做法,但是如果您将一个名为“Add”的方法添加到将对象序列化到磁盘的类中,这也同样糟糕。As easy as it is to complain about overloaded operators, as long as they don't act in suprising ways, I really don't see the issue. Yes there are bad examples out there (even in the stl). See auto_ptr's assignment operator for example. Overloading some operators, like
&&
,||
and,
is almost always going to be bad. But for the most part, make the operators do what they advertise and there's no real problem.It's bad practice to overload
operator+
to do something weird, but it's just as bad if you put a method called "Add" to your class that serialized the object to disk.重载运算符可能是完成某些事情的绝佳方法,但非常容易被滥用。
重载
<<
和>>
运算符可以轻松扩展 C++ 的流,无论是新类型的流、新的 I/O 对象还是两者。 重载->
使智能指针几乎成为 C++ 指针的直接替代品。 重载运算符使字符串连接运算符成为可能,并构建语法上与 int 类似的新数字类型。 有了它们,就可以在库中执行需要在其他语言中进行语言级别更改的操作。它们确实有其局限性。 没有适合求幂的运算符。 只有一种乘法运算符,并且在某些情况下有不止一种乘法方法(例如,对于 3D 向量,至少有点积和叉积)。
&&
、||
和逗号运算符无法复制其内置功能,因为它们不能具有短路求值和序列点。当然,它们也可能被滥用。 例如,没有语言要求算术运算符必须像算术一样工作。 我见过为了想出一个有人认为很直观的 SQL 表示法而做出的可怕的事情。 在写得很糟糕的 C++ 程序中,不可能知道
a = x * y;
的作用,因为它是a.operator=(x.operator*(y)) ;
,或者可能是a.operator=(operator*(x, y));
或者其他什么,并且可以编写运算符函数来执行任何操作。Bjarne Stroustrup 设计 C++ 的目的是包含有用的功能,而不考虑滥用的可能性,而 James Gosling 设计 Java 的目的是排除过度滥用的功能,即使它们有些用处。 我不清楚这些哲学是正确还是错误,但它们是不同的。
Java 旨在避免通常需要某些 C++ 功能的情况,例如运算符重载、多重继承和运行时类型推导,因此它们不会经常被错过。 这是好还是坏,或者两者都不是我不知道的。
至于教导学生,告诉他们不要自己重载运算符(除非在定义的条件下,例如函子和赋值运算符),但指出库如何使用重载运算符。 我不相信任何 C++ 学生能够正确地完成它们,如果他们能够做到,他们可以并且将会自学。 他们会知道这很棘手,因为你在课堂上禁止这样做。 有些我永远不会相信比
for
语句更复杂的东西会找出如何重载运算符,并且无论如何都会这样做,但这就是生活。Overloaded operators are potentially excellent ways to do certain things, but are horribly easy to abuse.
Overloading the
<<
and>>
operators makes it easy to extend C++'s streams, both in new kinds of streams, new objects for I/O, and both. Overloading->
makes smart pointers almost a drop-in replacement for C++ pointers. Overloaded operators make it possible to have a string concatenation operator, and to build up new sorts of numbers that are syntactically just likeint
s. Having them makes it possible to do things in libraries that would require language-level changes in other languages.They do have their limitations. There is no operator suitable for exponentiation. There is only one multiplication operator, and in some cases there's more than one way to multiply (with 3D vectors, for example, there's at least the dot and cross products). The
&&
,||
, and comma operators cannot replicate their built-in functionality, since they can't have short-circuit evaluations and sequence points.And, of course, they can be abused. There's no language requirement, for example, that arithmetic operators have to work anything like arithmetic. I've seen horrible things done in an effort to come up with a SQL notation that somebody thought was intuitive. In a C++ program that was badly written, it's impossible to know what, say,
a = x * y;
does, since it'sa.operator=(x.operator*(y));
, or maybea.operator=(operator*(x, y));
or something, and the operator functions could be written to do anything.Bjarne Stroustrup's intention in designing C++ was to include useful features regardless of the possibility of abuse, whereas James Gosling's intention in designing Java was to exclude excessively abusable features even if they were somewhat useful. It's not clear to me that either of those philosophies is correct or incorrect, but they are different.
Java was designed to avoid situations that would usually call for some C++ features, like operator overloading, multiple inheritance, and run-time type deduction, so they aren't often missed. Whether this is good or bad or neither is not something I know.
As far as teaching students, tell them not to overload operators themselves (except under defined conditions, such as functors and the assignment operator), but point out how the library uses overloaded operators. I wouldn't trust any C++ student to do them right, and if they're going to be able to do it they can and will learn it on their own. They will know it's tricky, because you forbade it in class. Some of the ones I would never trust with anything more complicated than a
for
statement will find out how to overload operators, and will do anyway, but that's life.运算符重载对于很多目的来说都是非常重要的。 如果没有重载operator()的能力,函子就不可能被创建。
在许多情况下,通用编程会变得令人头疼。 如果我编写一个数值算法,我会依赖行为相同的值类型,无论它是 float、double、std::complex 还是某种自制类型。 我依赖于定义的常用算术运算符,因此我不必为内置类型编写单独的重载,也不必为自定义类型编写另一个重载。
智能指针依赖于能够重载取消引用运算符的对象,以便它们可以像指针一样运行。
运算符重载对于使 C++ 编程变得可以忍受非常重要。
至于复杂,我只是看不出来。 它并不比创建自己的函数更复杂,人们通常认为创建自己的函数相当容易。 如果你将它命名为“multiply”,它就是一个函数,如果你将它命名为“operator*”,它就是一个运算符。 但正文中的代码是完全相同的。
当然,运营商有时会受到滥用。 并且<< 或>> 可能是可以接受的,但它们是如此众所周知和使用,所以我认为这是公平的。
然而,如果您询问 C# 等语言中的运算符重载,我很乐意不使用它们。 它们的实现要尴尬得多,它们不能使用泛型,并且不能启用 C++ 使用的所有好用且方便的技巧。
Operator overloading is pretty essential for a lot of purposes. Functors would be impossible to create without the ability to overload operator().
Generic programming would in many cases become a pain in the butt. If I write a numerical algorithm, I rely on the value type behaving the same whether it's a float, double, std::complex or some home-brewed type. I rely on the usual arithmetic operators being defined, and such, so I don't have to write a separate overload for built-in types, and another for custom ones.
Smart pointers rely on objects being able to overload the dereferencing operator so that they can behave like pointers.
Operator overloading is extremely important for making c++ programming bearable.
As for it being complicated, I just don't see it. It's no more complicated than creating your own function, which people generally find fairly easy. If you name it "multiply" it is a function, if you name it "operator*", it's an operator. But the code in the body is the exact same.
Of course operators sometimes get abused. And << or >> may be borderline acceptable, but they're so commonly known and used that I think it's fair.
If you'd asked about operator overloading in something like C#, however, I'd gladly do without them. Their implementation is much more awkward, they don't work with generics, and they don't enable all the nice and convenient tricks that C++ uses.
我认为这确实取决于用例。 有几种类型的类需要运算符。 例如,如果没有 -> ,智能指针将毫无价值。 和 * 运算符。
我还发现比较、相等和赋值运算符对于特定类型非常有用。 我在编辑器环境中工作,因此我们自然有几次来表示坐标和跨度。 当然,我们可以用比较运算符做所有事情,但是
Just 看起来负载比
我发现的其他运算符的用处要少,尽管强制转换偶尔会派上用场。
I think it really depends on the use case. There are several types of classes for which operators are a necessity. For instance, smart pointers would be worthless without the -> and * operators.
I also find the comparision, equality and assignment operators to be very useful for specific types. I work in an editor environment and as such we naturally have several times for representing coordinates and spans. Sure we could do everything with a comparison operator but
Just looks loads better than
I find less use for the other operators although cast occasionally comes in handy.
我不认为运算符重载是一个坏主意。 我确实认为将隐式转换设置为默认行为是一个坏主意。 默认隐式转换与运算符重载相结合是一个非常坏主意。
完全取消隐式转换 - 或者使其依赖于“隐式”关键字 - 并且该语言永远不会有无数文章中讨论的潜在陷阱和陷阱,例如 这个。
I don't think operator overloading was a bad idea. I do think that making implicit conversion the default behavior was a bad idea. And default implicit conversion in combination with operator overloading is a really bad idea.
Take away implicit conversion entirely -- or make it dependent on an "implicit" keyword -- and the language would never have had the number of potential pitfalls and gotchas discussed in countless articles like this one.
正如 Neil 在他的回答中指出的那样,运算符重载是学习优秀的面向对象 C++ 习惯用法的必要主题。 我会警告学生,如果你不按照惯用约定实现重载运算符,它可能会导致非常错误和意外的行为。 运算符重载并不是发挥创造力的好时机。
As Neil points out in his answer, operator overloading is a necessary topic to learn good object-oriented C++ idioms. I would teach it with a caution to students that if you don't implement overloaded operators following idiomatic conventions, it can lead to very buggy and unexpected behavior. Operator overloading is not a good time to be creative.
运算符及使用时的情况:
运算符->、运算符* - 用于代理对象和不同的包装器。
运算符= - 需要避免复制时出现意外行为。
运算符 < (>, <=, >=) - 用于存储在映射或集合中(但通常最好将函子传递给这个)。
运算符 << ( >> ) - 用于流和 boost::lexical_cast 兼容性。
运算符 ==、!= - 允许比较对象。
操作员 ! - 有时改为 valid() 函数。
运算符类型 - 用于转换为其他类型。
operator() - 用于智能函子,当不允许 boost 时。
仅此而已。
有时以前我使用过其他运算符,但那是为了我的数学实用程序。
还应该小心逻辑运算符(&&、||) - 我们将与标准语义有所不同:
如果我们重载了运算符&&,可能会有其他含义。
Operators and cases when I used them:
operator->, operator* - for proxy objects and different wrappers.
operator= - needed for avoid unexpected behavior on copy.
operator < (>, <=, >=) - for store in map or set (but usualy better to pass functor into this one ).
operator << ( >> ) - for streams and boost::lexical_cast compatibility.
operator ==, != - for allow compare objects.
operator ! - sometimes instead valid() function.
operator Type - for conversion into other type.
operator() - for smart functor, when boost was disallowed.
that's all.
Sometimes ago I've used other operators, but that was for my mathematic utils.
Also should be careful with logical operators (&&, ||) - we will have difference with standard semantic:
could have other sense if we have overloaded operator&&.
我真的很喜欢 C++ 中为非内置类型重载算术运算符的能力。 但仅适用于具有类似算术行为的类型; 例如定点类、3D 向量类、复数类、任意长度的“bignum”类。 我已经用 Java 编写了类似的代码,并且因必须编写诸如
a.Add(b)
而不是a+b
之类的内容而感到恼火。 请注意,我是一名受过训练的数学家; 在我看来,运算符重载可以让您在 C++ 中获得一点特定领域语言的优点,而无需实际实现。但当我看到例如
operator+
的功能超载时,这真的让我很恼火,而这些功能最好由operator<<
(遵循狡猾但完善的 iostream 约定)或 STL 来完成类似.push_back()
的模式。至于
operator()
...发现boost::bind
&boost::function
我无法想象没有函子的生活。 如果没有重载operator*
、operator->
等,智能指针就不会那么方便。I really like the ability to overload arithmetic operators for non-builtin types in C++. But only for types with arithmetic-like behaviour; for example fixed point classes, 3D vector classes, complex number classes, arbitrary length "bignum" classes. I've written similar code in Java and been annoyed by having to write things like
a.Add(b)
instead ofa+b
. Mind you, I'm a mathematician by training; seems to me operator overloading lets you get a little bit of domain-specific-language goodness in C++ without having to actually implement one.But it really annoys me when I see e.g
operator+
being overloaded with functionality which would better be done byoperator<<
(following the dodgy but well established iostream convention) or STL.push_back()
-like patterns.As for
operator()
... post discoveringboost::bind
&boost::function
I can't imagine life without functors. And smart pointers wouldn't be nearly as convenient without overloadedoperator*
,operator->
etc.重载运算符的问题在于,有些人喜欢用相对于运算符的原始 (C) 目的而言没有任何意义的功能来重载它们(这里我指的是 >> 和 << std::iostream 中的运算符。)。 在我看来,您应该重载运算符的唯一时间是当重载完全匹配底层运算符的含义时(即 < 和 > 必须是比较。)或者,您必须以某种方式重载它才能进行交互与另一个图书馆。
坦率地说,除非我需要一个库,否则我根本不会重载运算符。 它只会让读者费力去弄清楚发生了什么。
The problem with overloading operators is that some people like to overload them with functionality that doesn't really make any sense in relation to the original (C) purpose of the operator (here I point at the >> and << operators in the std::iostream stuff.). In my mind, the only time you should over-load operators is when either the overload EXACTLY matches the underlying operator's meaning (i.e. < and > must be comparisons.) OR, you must overload it in a certain way in order to interact with another library.
Frankly, I won't overload an operator at all unless I need to for a library. It just makes the reader work too hard to figure out what's going on.
最好避免重载的运算符之一是 转换运算符。 它会导致意想不到的结果,甚至 STL 也不喜欢重载它,而是更喜欢函数风格转换:
One of the operators that you had better avoid to overload is conversion operator. It leads to such unexpected results that even STL prefers not to overload it and instead prefers function style conversion:
我怀念 Java 中的重载运算符,尤其是在以下情况下:
作为算法或函子的类:
(策略模式、责任链、解释器等)。 很自然的事情就是重载 op(); 相反,每个程序员都会为函数提出(通常是不兼容的,因此令人困惑的)名称:“eval”,“evaluate”,“operation”,“doIt”等。因此清晰度降低了,因为我们被迫使用这些名称不要让他们的意思显而易见。
具有到另一种类型的转换的类:
在 C++ 中,它是运算符 Type(),既可用于实际转换,也可用于生成所需类的内部成员。 第二种情况在 Java 中经常出现,当一个类不必要是 Final 的,但您想向其添加操作时:
由于 DecoratedStringBuffer 不是 is-a StringBuffer,因此在它离开您的代码并返回到客户端代码之前,需要将其添加为 is-a StringBuffer。大概是通过最终应用后缀和前缀的函数转换回来的。 如果我们可以调用该运算符 StringBuffer(),那就太好了(如果 Java 像 C++ 一样可以应用一种用户提供的转换,那就更好了)。
相反,因为没有约定,我们必须给它起一个必然更加模糊的名称。 getStringBuffer() 是一个显而易见的名称,但对于许多 Java 用户来说,这意味着一个相应的 setStringBuffer,而我们并不想要它。 即使它并不暗示这一点,这个名称也是含糊不清的:您正在获取的 StringBuffer 是我们操作的 StringBuffer,还是其他东西?
toStringBuffer() 是一个更好的名字,也是我倾向于应用的模式,但是读代码的人想知道为什么看起来像 getter 的东西被称为“to”ClassName。
老实说,除了设计数字类或“明显”可连接对象之外,重载 op+ 几乎没有什么用处。 由于 Java 不像 C++ 那样基于值,因此 op=; 没有太多用处。 Java 并不试图让所有东西都像原始 int 值类一样。 我怀念的是 op() 和转换运算符。
I miss having overloaded operators in Java, especially in the following situations:
A class that is an algorithm or a functor:
(Strategy Pattern, Chain of Responsibility, Interpreter, etc.). The natural thing is to overload op(); instead, every programmer comes up with (often incompatible and thus confusing) names for functions: "eval", "evaluate", "operation", "doIt", etc. So clarity is reduced, because these names we're forced to use don't make their meaning obvious.
A class that has a conversion to another type:
In C++ that's operator Type(), and works both for actual conversion and for yielding an internal member of the desired class. The second case comes up a lot in Java when a class is unnecessarily final but you want to add operations to it:
Since DecoratedStringBuffer isn't is-a StringBuffer, before it leaves your code and returns to the client code, it needs to be converted back, presumably by a function that finally applies the suffix and prefix. It would be great if we could call that operator StringBuffer() (and even greater if Java, like C++, could apply one user-supplied conversion).
Instead, because there's no convention, we have to give it a name that is necessarily more ambiguous. getStringBuffer() is one obvious name, but to many Java users, that'll imply a corresponding setStringBuffer, which we don't want. Even if it doesn't imply that, the name's ambiguous: is the StringBuffer you're getting the one we operate on, or something else?
toStringBuffer() is a better name, and the pattern I tend to apply, but then someone reading the code wonders why what looks like a getter is called "to"ClassName.
Honestly, except in designing numerical classes or "obviously" cocatenatable objects, there's little use for overloading op+. And as Java isn't value-based like C++, there's not much use for op=; Java isn't trying to make everything act like a primitive int value class. It's op() and the conversion operators I miss.
拥有这个功能固然很好,但并不是必需的。 在很多情况下,我发现它们比它们所带来的好处更令人困惑。 就像 JaredPars 示例中一样,.compare 函数比 '>' 更明显 大部头书。 我们可以直接看出 point1 是一个对象而不是原始数据类型。 我喜欢在我使用的库中使用重载运算符,但在我自己的代码中我很少使用它们。
编辑:我选择的函数名称不一致。 将 .compare 替换为 .greaterThan 会更清晰。 我的意思是,对我来说,绑定到对象的函数名称比乍一看与对象没有关联的运算符更明显。 恕我直言,精心选择的函数名称更容易阅读。
It's a nice to have feature but not an essential one. In many cases I find them more confusing than the benefits they have. Like in JaredPars example, the .compare function is more obvious than a '>' to me. One see's directly that point1 is an object and not a primitive data type. I like overloaded operators in libs I use but in my own code I use them very seldom.
EDIT: my choice of the function name is inconsistent. Replace .compare with .greaterThan makes it clearer. What I mean is, that a function name bound to object for me is more obvious than an operator that has no ascociation with the object on the first look. Imho a well chosen function name is easier to read.