操作员超载的基本规则和习惯是什么?
注意:答案以特定的顺序给出,并且随着时间的推移获得了不同数量的选票。由于答案的顺序取决于您的答案排序偏好,因此答案的 索引 在它们最有意义的顺序中:
- c ++
- C ++
- in -c/4421729#4421729“>成员与非 通用运营商超载
- 分配操作员
- 流插入和提取
- 功能调用操作员
- 逻辑运算符
- 算术运算符
- 下标操作员
- 类似指针类型的操作员
- 比较操作员,包括C ++ 20三路比较
- 转换操作员
- a href =“ https://stackoverflow.com/questions/4421706/operator-operator-overloading-in-c/4421791#4421791”> edete and delete
- 规范函数签名的摘要
(注意:这是堆栈溢出的C ++常见问题解答。如果您想批评以这种形式提供常见问题的想法,则启动所有这些的Meta上的发布将是这样做的地方。该问题的答案在 C ++聊天室中,FAQ Ideas首先开始的地方,所以您的答案很可能会被那些想到这个想法的人阅读。)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
普通运算符
过载超载操作员大部分工作的 是样板代码。这并不奇怪,因为操作员只是句法糖。他们的实际工作可以通过(通常被转发到)普通功能来完成。但是重要的是要正确获取此样板代码。如果失败,则操作员的代码不会编译,您的用户代码将不会编译,或者您的用户代码的行为会出奇。
作业操作员
关于作业有很多话要说。但是,大多数已经在 ,所以我将在这里跳过大部分,仅列出完美的分配运算符以供参考:
流插入和提取免责
<<
andand
>>>>>
作为位移动运算符,跳到二进制算术操作员节。位移动运算符
<<
and>>
,尽管仍用于硬件接口用于其从C所继承的位操作函数,但已变得更加普遍为在大多数应用程序中,过载的流输入和输出运算符。流操作员是最常见的超载运算符,是二进制输液运算符,该语法未针对其是否应为成员还是非会员指定任何限制。
但是,他们的左操作数是来自标准库的流,您不能将成员函数添加到 1 >中,因此您需要作为非成员功能 2 < 1 /sup>。
两者的规范形式是:
实施
运算符时,只有在阅读本身成功时,需要手动设置流的状态,但结果不是预期的结果。
1请注意,标准库的某些
&lt;&lt;&lt;&lt;
过载是作为成员函数实现的,有些是免费功能。只有语言环境依赖性函数是成员函数,例如运算符&lt;&lt;(long)
。2 根据经验规则,插入/提取操作员应该是成员功能,因为它们会修改左操作数。但是,我们不能遵循此处的经验规则。
函数调用操作员
函数调用操作员,用于创建函数对象,也称为函数,必须定义为a 成员 函数,因此它始终具有隐式
此成员函数的参数。除此之外,它可以超载以获取包括零在内的任何其他参数。
这是语法:用法的示例
:
在整个C ++标准库中,函数对象始终被复制。因此,您自己的功能对象应该便宜。如果函数对象绝对需要使用昂贵的数据,则最好将该数据存储在其他地方并具有函数对象。
比较操作员
==
,!=
,&lt;
,&gt;
,&lt; =
和&gt; =
运算符以及&lt; =&gt;
三路比较,也就是。 C ++ 20中的“飞船运营商”。关于比较操作员有太多的话要说,它将超过此答案的范围。在最简单的情况下,您可以通过默认
&lt; =&gt;
在 c ++ 20 :如果不能这样做,请继续链接答案。
逻辑运算
符unary前缀否定
!
应作为成员函数实现。由于它是多么罕见和令人惊讶,因此通常不是一个好主意。其余的二进制逻辑运算符(
||
,&amp;&amp;
)应作为免费功能实现。但是,这是非常,您不太可能为这些 1 找到合理的用例。1应注意的是,内置版本的
||
和&amp;&amp;&amp;
使用快捷方式语义。虽然用户定义的(因为它们是用于方法调用的句法糖),但不使用快捷语义。用户将期望这些操作员具有捷径语义,并且它们的代码可能取决于它,因此,强烈建议不要定义它们。算术运算符单位算术运算符,
Unary Arithmetic Operators
Unary增量和减少操作员都在前缀和后缀风味中均具有。要从另一个告诉一个,后缀变体会采取其他虚拟int论点。如果您超负荷增加或减少,请确保始终实现前缀和后缀版本。
这是增量的规范实现,减少遵循相同的规则:
请注意,后缀变体是根据前缀实现的。另请注意,Postfix进行了额外的副本。 1
超负荷单位负载,加上并不是很常见,并且可能避免使用。如果需要,它们可能应作为成员函数重载。
1还请注意,后缀变体的工作量更多,因此使用效率较低,而不是前缀变体。这是通常更喜欢前缀增量而不是后缀增量的一个很好的理由。虽然编译器通常可以优化内置类型的后缀增量的其他工作,但它们可能无法对用户定义的类型进行相同的操作(这可能是看起来像列表迭代器一样无辜的东西)。一旦您习惯了
i ++
,就很难记住++ i
而不是i
不是内置的类型(加上更改类型时,您必须更改代码),因此最好养成始终使用前缀增量的习惯,除非明确需要后缀。二进制算术算术运算符的二进制算术运算
符,不要忘记服从第三个基本规则操作员的重载:如果您提供
+
,也提供+=
,如果您提供-
,请勿省略- =
等。是第一个观察到复合分配操作员可以用作其非混合物的基础的人。也就是说,操作员+
是根据+=
, - 实现的 根据我们的经验规则,
+
及其同伴应为非会员,而他们的复合分配对应物(+=
等),更改其左参数,应该是成员。这是+=
和+
的示例代码;其他二进制算术运算符应以相同的方式实现:ocerator+=
每个参考返回其结果,而operator+
返回其结果的副本。当然,返回参考通常比返回副本更有效,但是在操作员+
的情况下,副本没有办法。当您编写a + b
时,您希望结果是一个新值,这就是为什么操作员 +
必须返回一个新值。 1另请注意,
运算符+
可以通过lhs
逐个值而不是参考来稍微缩短。但是,这将泄漏实现细节,使功能签名非对称,并阻止命名返回值优化其中
result
与返回的对象是相同的对象。有时,用
@=
(例如hortrix乘法)实现@
是不切实际的。在这种情况下,您还可以将
@=
委派给@
:位操作操作员
〜
&amp;
|
^
&lt;&lt;
&gt;&gt;&gt;
应以与算术运算符相同的方式实现。但是,(除了过载&lt;&lt;
和&gt;&gt;
用于输出和输入),几乎没有合理的用例来超载这些。1再次,从此获得的课程是
a + = b
通常比a + b 如果可能的话,应首选。
下标操作员下
标操作员是二进制运营商,必须作为类成员实现。它用于类似容器的类型,可以通过键访问其数据元素。
提供这些提供的规范形式是:
除非您不希望班级的用户能够更改
operator []
返回的数据元素(在这种情况下,您可以省略非const型号) ,您应始终提供操作员的两个变体。用于定义自己的迭代器或智能指针的类似于指针类型的操作员
,您必须超负荷Unary前缀解码操作员
*
和二进制Infix Pointer Pointer Member访问操作员- &gt;
:请注意,这些几乎总是需要const和非const版本。
对于
- &gt;
运算符,如果value_type
是class
(或struct> struct> struct
或code> union> Union
code>)类型,另一个运算符 - &gt;()
被递归地调用,直到operator-&gt;()
返回非类型的值。一级运营商绝不应超载。
对于
操作员 - &gt;*()
(以及有关operator-&gt;
的更多详细信息)参见这个问题。操作员 - &gt;*()
很少使用,因此很少过多。实际上,即使迭代器也不会超载。继续进行转换操作员。
Common Operators to Overload
Most of the work in overloading operators is boilerplate code. That is little wonder, since operators are merely syntactic sugar. Their actual work could be done by (and often is forwarded to) plain functions. But it is important that you get this boilerplate code right. If you fail, either your operator’s code won’t compile, your users’ code won’t compile, or your users’ code will behave surprisingly.
Assignment Operator
There's a lot to be said about assignment. However, most of it has already been said in GMan's famous Copy-And-Swap FAQ, so I'll skip most of it here, only listing the perfect assignment operator for reference:
Stream Insertion and Extraction
<<
and>>
as bitwise shift operators, skip to the section Binary Arithmetic Operators.The bitwise shift operators
<<
and>>
, although still used in hardware interfacing for the bit-manipulation functions they inherit from C, have become more prevalent as overloaded stream input and output operators in most applications.The stream operators, among the most commonly overloaded operators, are binary infix operators for which the syntax does not specify any restriction on whether they should be members or non-members.
However, their left operands are streams from the standard library, and you cannot add member functions to those1, so you need to implement these operators for your own types as non-member functions2.
The canonical forms of the two are these:
When implementing
operator>>
, manually setting the stream’s state is only necessary when the reading itself succeeded, but the result is not what would be expected.1 Note that some of the
<<
overloads of the standard library are implemented as member functions, and some as free functions. Only the locale-dependent functions are member functions, such asoperator<<(long)
.2 According to the rules of thumb, the insertion/extraction operators should be member functions because they modify the left operand. However, we cannot follow the rules of thumb here.
Function Call Operator
The function call operator, used to create function objects, also known as functors, must be defined as a member function, so it always has the implicit
this
argument of member functions. Other than this, it can be overloaded to take any number of additional arguments, including zero.Here's an example of the syntax:
Usage:
Throughout the C++ standard library, function objects are always copied. Your own function objects should therefore be cheap to copy. If a function object absolutely needs to use data which is expensive to copy, it is better to store that data elsewhere and have the function object refer to it.
Comparison Operators
==
,!=
,<
,>
,<=
, and>=
operators, as well as the<=>
three-way comparison, aka. "spaceship operator" in C++20. There is so much to say about comparison operators that it would exceed the scope of this answer.In the most simple case, you can overload all comparison comparison operators by defaulting
<=>
in C++20:If you can't do this, continue to the linked answer.
Logical Operators
The unary prefix negation
!
should be implemented as a member function. It is usually not a good idea to overload it because of how rare and surprising it is.The remaining binary logical operators (
||
,&&
) should be implemented as free functions. However, it is very unlikely that you would find a reasonable use case for these1.1 It should be noted that the built-in version of
||
and&&
use shortcut semantics. While the user defined ones (because they are syntactic sugar for method calls) do not use shortcut semantics. User will expect these operators to have shortcut semantics, and their code may depend on it, Therefore it is highly advised NEVER to define them.Arithmetic Operators
Unary Arithmetic Operators
The unary increment and decrement operators come in both prefix and postfix flavor. To tell one from the other, the postfix variants take an additional dummy int argument. If you overload increment or decrement, be sure to always implement both prefix and postfix versions.
Here is the canonical implementation of increment, decrement follows the same rules:
Note that the postfix variant is implemented in terms of prefix. Also note that postfix does an extra copy.1
Overloading unary minus and plus is not very common and probably best avoided. If needed, they should probably be overloaded as member functions.
1 Also note that the postfix variant does more work and is therefore less efficient to use than the prefix variant. This is a good reason to generally prefer prefix increment over postfix increment. While compilers can usually optimize away the additional work of postfix increment for built-in types, they might not be able to do the same for user-defined types (which could be something as innocently looking as a list iterator). Once you got used to do
i++
, it becomes very hard to remember to do++i
instead wheni
is not of a built-in type (plus you'd have to change code when changing a type), so it is better to make a habit of always using prefix increment, unless postfix is explicitly needed.Binary Arithmetic Operators
For the binary arithmetic operators, do not forget to obey the third basic rule operator overloading: If you provide
+
, also provide+=
, if you provide-
, do not omit-=
, etc. Andrew Koenig is said to have been the first to observe that the compound assignment operators can be used as a base for their non-compound counterparts. That is, operator+
is implemented in terms of+=
,-
is implemented in terms of-=
, etc.According to our rules of thumb,
+
and its companions should be non-members, while their compound assignment counterparts (+=
, etc.), changing their left argument, should be a member. Here is the exemplary code for+=
and+
; the other binary arithmetic operators should be implemented in the same way:operator+=
returns its result per reference, whileoperator+
returns a copy of its result. Of course, returning a reference is usually more efficient than returning a copy, but in the case ofoperator+
, there is no way around the copying. When you writea + b
, you expect the result to be a new value, which is whyoperator+
has to return a new value.1Also note that
operator+
can be slightly shortened by passinglhs
by value, not by reference.However, this would be leaking implementation details, make the function signature asymmetric, and would prevent named return value optimization where
result
is the same object as the one being returned.Sometimes, it's impractical to implement
@
in terms of@=
, such as for matrix multiplication.In that case, you can also delegate
@=
to@
:The bit manipulation operators
~
&
|
^
<<
>>
should be implemented in the same way as the arithmetic operators. However, (except for overloading<<
and>>
for output and input) there are very few reasonable use cases for overloading these.1 Again, the lesson to be taken from this is that
a += b
is, in general, more efficient thana + b
and should be preferred if possible.Subscript Operator
The subscript operator is a binary operator which must be implemented as a class member. It is used for container-like types that allow access to their data elements by a key.
The canonical form of providing these is this:
Unless you do not want users of your class to be able to change data elements returned by
operator[]
(in which case you can omit the non-const variant), you should always provide both variants of the operator.Operators for Pointer-like Types
For defining your own iterators or smart pointers, you have to overload the unary prefix dereference operator
*
and the binary infix pointer member access operator->
:Note that these, too, will almost always need both a const and a non-const version.
For the
->
operator, ifvalue_type
is ofclass
(orstruct
orunion
) type, anotheroperator->()
is called recursively, until anoperator->()
returns a value of non-class type.The unary address-of operator should never be overloaded.
For
operator->*()
(and more details aboutoperator->
) see this question.operator->*()
is rarely used and thus rarely ever overloaded. In fact, even iterators do not overload it.Continue to Conversion Operators.
C ++中运算符超载的三个基本规则
在C ++中超载时,有 三个基本规则,您应该遵循 。与所有此类规则一样,确实有例外。有时人们会偏离他们,结果并不是糟糕的代码,但是这种积极的偏差很少,而且相距甚远。至少,在我见过的100个这样的偏差中,有99个是没有道理的。但是,它可能同样是1000中的999。因此,您最好坚持以下规则。
每当操作员的含义显然清晰且无可争议的时,它不应超负荷。 而不是为了提供一个精心选择的名称。
基本上,从本质上说:不要这样做。这似乎很奇怪,因为关于操作员的超载有很多了解,因此许多文章,书籍章节和其他文本都涉及所有这些。但是,尽管有看似明显的证据,但很少有一个案例使操作员超负荷是适当的。原因是实际上很难理解操作员应用背后的语义,除非在应用程序域中使用操作员是众所周知且无可争议的。与普遍的看法相反,情况并非如此。
始终坚持操作员的知名语义。
C ++对超载运算符的语义没有限制。您的编译器将愉快地接受实现二进制
+
操作员从其正确操作数中减去的代码。但是,从b
中减去a
,这种操作员的用户永远不会怀疑表达式a + b
。当然,这假定应用程序域中的操作员的语义是无可争议的。始终提供一组相关操作。
运营商相互关联以及其他操作。如果您的类型支持
a + b
,则用户也希望能够调用a + = b
。如果它支持前缀增量++ a
,他们将期望a ++
也可以正常工作。如果他们可以检查a&lt; b
,他们肯定会期望能够检查a&gt; B
。如果他们可以复制您的类型,他们希望任务也可以工作。继续进行成员与非成员之间的决定。
The Three Basic Rules of Operator Overloading in C++
When it comes to operator overloading in C++, there are three basic rules you should follow. As with all such rules, there are indeed exceptions. Sometimes people have deviated from them and the outcome was not bad code, but such positive deviations are few and far between. At the very least, 99 out of 100 such deviations I have seen were unjustified. However, it might just as well have been 999 out of 1000. So you’d better stick to the following rules.
Whenever the meaning of an operator is not obviously clear and undisputed, it should not be overloaded. Instead, provide a function with a well-chosen name.
Basically, the first and foremost rule for overloading operators, at its very heart, says: Don’t do it. That might seem strange, because there is a lot to be known about operator overloading and so a lot of articles, book chapters, and other texts deal with all this. But despite this seemingly obvious evidence, there are only a surprisingly few cases where operator overloading is appropriate. The reason is that actually it is hard to understand the semantics behind the application of an operator unless the use of the operator in the application domain is well known and undisputed. Contrary to popular belief, this is hardly ever the case.
Always stick to the operator’s well-known semantics.
C++ poses no limitations on the semantics of overloaded operators. Your compiler will happily accept code that implements the binary
+
operator to subtract from its right operand. However, the users of such an operator would never suspect the expressiona + b
to subtracta
fromb
. Of course, this supposes that the semantics of the operator in the application domain is undisputed.Always provide all out of a set of related operations.
Operators are related to each other and to other operations. If your type supports
a + b
, users will expect to be able to calla += b
, too. If it supports prefix increment++a
, they will expecta++
to work as well. If they can check whethera < b
, they will most certainly expect to also to be able to check whethera > b
. If they can copy-construct your type, they expect assignment to work as well.Continue to The Decision between Member and Non-member.
成员和非成员
[]
,(),=
,- &gt; < /code>,...
- &gt;*
++
,-在
*=
,...+
,==
,&lt; = &gt;
,/
,...二进制运算符
=
(分配),[]
(数组订阅),- &gt;
(成员访问)以及n-ary()
(函数调用)操作员,必须始终以 成员功能 ,因为该语言的语法需要它们。其他运营商可以作为成员或非会员实施。但是,其中一些通常必须作为非成员功能实现,因为您的左操作数不能由您修改。其中最突出的是输入和输出操作员
&lt;&lt;
and&gt;&gt;
,其左操作数是来自标准库的流类,您无法更改。对于所有必须选择以成员函数或非成员函数实现它们的操作员, 使用以下经验规则 来决定:
当然,与所有经验法则一样,也有例外。如果您有类型
,并且要超载增量和减少运算符,则不能作为成员函数执行此操作,因为在C ++中,枚举类型不能具有成员函数。因此,您必须将其作为免费功能超载。和
运算符&lt;()
对于嵌套在类模板中的类模板,当在类定义中以成员函数为内联时,编写和读取要容易得多。但是这些确实是罕见的例外。(但是,如果您有例外,请不要忘记操作数的
const
ness的问题,对于成员函数而言,它成为隐式此
代码>参数。如果运算符作为非成员函数将最左边的参数作为const
参考>最后要进行*this
aconst
参考。)继续通用操作员超载。
The Decision between Member and Non-member
[]
,()
,=
,->
, ...->*
++
,-
,*
,new
, ...+=
,|=
,*=
, ...+
,==
,<=>
,/
, ...The binary operators
=
(assignment),[]
(array subscription),->
(member access), as well as the n-ary()
(function call) operator, must always be implemented as member functions, because the syntax of the language requires them to.Other operators can be implemented either as members or as non-members. Some of them, however, usually have to be implemented as non-member functions, because their left operand cannot be modified by you. The most prominent of these are the input and output operators
<<
and>>
, whose left operands are stream classes from the standard library which you cannot change.For all operators where you have to choose to either implement them as a member function or a non-member function, use the following rules of thumb to decide:
Of course, as with all rules of thumb, there are exceptions. If you have a type
and you want to overload the increment and decrement operators for it, you cannot do this as a member functions, since in C++, enum types cannot have member functions. So you have to overload it as a free function. And
operator<()
for a class template nested within a class template is much easier to write and read when done as a member function inline in the class definition. But these are indeed rare exceptions.(However, if you make an exception, do not forget the issue of
const
-ness for the operand that, for member functions, becomes the implicitthis
argument. If the operator as a non-member function would take its left-most argument as aconst
reference, the same operator as a member function needs to have aconst
at the end to make*this
aconst
reference.)Continue to Common operators to overload.
C ++中运算符过载的一般语法
您不能更改C ++内置类型的操作员的含义,只能对用户定义的类型 1 才能重载运算符。也就是说,至少一个操作数必须是用户定义的类型。与其他超载功能一样,只有一次,可以将操作员重载一次。
并非所有操作员都可以在C ++中超载。在无法重载的运算符中,是:
。
::
sizeof
typeID
。*
以及C ++,?:
在C ++中可以重载的唯一三元运算符是:
+
/-
*
%
和+=
- =
*=
/=
%=
+
-
++
-
&amp;
|
> ^
&lt;&lt;
&gt;&gt;
and&amp; =
| =
^=
&lt;&lt; =
&gt;&gt; =
〜
==
!=
&lt;
&gt;
&lt; =
&gt; =
&lt; =&gt;
| | |
&amp;&amp;
!
new
new []
deletedelete [delete [ ]
t
- &gt;
- &gt;*
*
&amp;
()
[]
co_await
,
二进制 您可以超载所有这些并不意味着您应该这样做。参见下一个答案。
在C ++中,运算符以特殊名称 的形式超载。与其他功能一样,超载运算符通常可以用作其左操作数 的 成员功能,也可以作为 non-ember函数 。您是否可以自由选择或必定使用任何一个取决于几个条件。 3 一个单一操作员
@
4 ,应用于对象x ,将其调用为operator@(x)
或x.operator@()
。应用于对象x
和y
的二进制ifix运算符@
被称为operator@(x,y)< /code>或as
x.operator@(y)
。 5作为非成员功能实现的操作员有时是其操作数类型的朋友。
1术语“用户定义”可能会稍微误导。 C ++可以区分内置类型和用户定义类型。前者属于INT,char和double; 包括标准库中的类型,即使不是由用户定义的。
后者属于所有结构,类,联合和枚举类型, 下标运算符曾经是二进制的,直到C ++ 23。
3 此FAQ的后期部分。
问题/4421706/operator-overloading/ 4421729 C ++中的有效操作员,这就是为什么我将其用作占位符的原因。
5 C ++中唯一的三元运算符不能超载,并且必须始终实现唯一的n-ary操作员作为成员函数。
继续。
The General Syntax of operator overloading in C++
You cannot change the meaning of operators for built-in types in C++, operators can only be overloaded for user-defined types1. That is, at least one of the operands has to be of a user-defined type. As with other overloaded functions, operators can be overloaded for a certain set of parameters only once.
Not all operators can be overloaded in C++. Among the operators that cannot be overloaded are:
.
::
sizeof
typeid
.*
and the only ternary operator in C++,?:
Among the operators that can be overloaded in C++ are these:
+
-
*
/
%
and+=
-=
*=
/=
%=
+
-
++
--
&
|
^
<<
>>
and&=
|=
^=
<<=
>>=
~
==
!=
<
>
<=
>=
<=>
||
&&
!
new
new[]
delete
delete[]
T
=
->
->*
*
&
()
[]
co_await
,
However, the fact that you can overload all of these does not mean you should do so. See the next answer.
In C++, operators are overloaded in the form of functions with special names. As with other functions, overloaded operators can generally be implemented either as a member function of their left operand's type or as non-member functions. Whether you are free to choose or bound to use either one depends on several criteria.3 A unary operator
@
4, applied to an object x, is invoked either asoperator@(x)
or asx.operator@()
. A binary infix operator@
, applied to the objectsx
andy
, is called either asoperator@(x,y)
or asx.operator@(y)
.5Operators that are implemented as non-member functions are sometimes friend of their operand’s type.
1 The term “user-defined” might be slightly misleading. C++ makes the distinction between built-in types and user-defined types. To the former belong for example int, char, and double; to the latter belong all struct, class, union, and enum types, including those from the standard library, even though they are not, as such, defined by users.
2 The subscript operator used to be binary, not N-ary until C++23.
3 This is covered in a later part of this FAQ.
4 The
@
is not a valid operator in C++ which is why I use it as a placeholder.5 The only ternary operator in C++ cannot be overloaded and the only n-ary operator must always be implemented as a member function.
Continue to The Three Basic Rules of Operator Overloading in C++.
转换运算符(也称为用户定义的转换)
在C ++中您可以创建转换操作员,允许编译器在类型和其他定义类型之间进行转换的操作员。转换操作员有两种类型的类型,即隐式和显式。
隐式转换运算符(C ++ 98/C ++ 03和C ++ 11)
隐式转换操作员允许编译器隐式转换(例如
int
和long )用户定义类型对其他类型的值。
以下是一个简单的类,带有隐式转换操作员:
隐式转换运算符,例如单词构造函数,是用户定义的转换。在试图将呼叫与超载函数匹配时,编译器将授予一个用户定义的转换。
起初,这似乎非常有帮助,但是问题在于,隐含的转换甚至在不期望的时候就开始启动。在以下代码中,
void f(const char*)
将被称为是因为my_string()
不是 lvalue ,所以第一个不匹配:初学者很容易弄错了这个错误,甚至经验丰富的C ++程序员有时会感到惊讶,因为编译器选择了,他们不怀疑的超负荷。这些问题可以通过明确的转换操作员来减轻。
与隐式转换操作员不同,显式转换操作员(C ++ 11)
,明确的转换操作员在不期望的情况下永远不会启动。以下是具有显式转换操作员的简单类:
注意
explicit
。现在,当您尝试从隐式转换运算符中执行意外的代码时,您会收到一个编译器错误:要调用显式铸造运算符,您必须使用
static_cast
,c-style cast或构造函数样式铸造(即t(value)
)。但是,有一个例外:允许编译器隐式转换为
bool
。此外,在将其转换为bool
之后,不允许编译器进行另一项隐式转换(允许编译器一次一次进行2个隐式转换,但只有1个用户定义的转换,以最大为max)。由于编译器不会施放“过去”
bool
,因此显式转换操作员现在删除了安全布尔习惯。例如,C ++ 11之前的智能指针使用安全的布尔成语来防止转化为积分类型。在C ++ 11中,Smart Pointers使用显式操作员,因为在将类型明确将类型转换为BOOL之后,不允许编译器隐式转换为积分类型。继续进行 Overloading
new
和
delete
delete delete delete 。Conversion Operators (also known as User Defined Conversions)
In C++ you can create conversion operators, operators that allow the compiler to convert between your types and other defined types. There are two types of conversion operators, implicit and explicit ones.
Implicit Conversion Operators (C++98/C++03 and C++11)
An implicit conversion operator allows the compiler to implicitly convert (like the conversion between
int
andlong
) the value of a user-defined type to some other type.The following is a simple class with an implicit conversion operator:
Implicit conversion operators, like one-argument constructors, are user-defined conversions. Compilers will grant one user-defined conversion when trying to match a call to an overloaded function.
At first this seems very helpful, but the problem with this is that the implicit conversion even kicks in when it isn’t expected to. In the following code,
void f(const char*)
will be called becausemy_string()
is not an lvalue, so the first does not match:Beginners easily get this wrong and even experienced C++ programmers are sometimes surprised because the compiler picks an overload they didn’t suspect. These problems can be mitigated by explicit conversion operators.
Explicit Conversion Operators (C++11)
Unlike implicit conversion operators, explicit conversion operators will never kick in when you don't expect them to. The following is a simple class with an explicit conversion operator:
Notice the
explicit
. Now when you try to execute the unexpected code from the implicit conversion operators, you get a compiler error:To invoke the explicit cast operator, you have to use
static_cast
, a C-style cast, or a constructor style cast ( i.e.T(value)
).However, there is one exception to this: The compiler is allowed to implicitly convert to
bool
. In addition, the compiler is not allowed to do another implicit conversion after it converts tobool
(a compiler is allowed to do 2 implicit conversions at a time, but only 1 user-defined conversion at max).Because the compiler will not cast "past"
bool
, explicit conversion operators now remove the need for the Safe Bool idiom. For example, smart pointers before C++11 used the Safe Bool idiom to prevent conversions to integral types. In C++11, the smart pointers use an explicit operator instead because the compiler is not allowed to implicitly convert to an integral type after it explicitly converted a type to bool.Continue to Overloading
new
anddelete
.Overloading
new
和delete
操作员注意: 这仅处理 语法 Overloading
new 和
new
和delete
,而不是使用 实现 。我认为超载的语义delete
值得 我永远无法做到这一点。他们自己的常见问题
,在操作员超载的主题中, /strong> 喜欢
新的t(arg)
评估此表达式时发生了两件事:第一个操作员新
< /em>被调用以获取原始内存,然后调用t
的适当构造函数将此原始内存转换为有效的对象。同样,当您删除对象时,首先调用其破坏者,然后将内存返回到operator delete
。C ++允许您调整这两个操作:在分配的内存下对象的内存管理和对象的构建/破坏。后者是通过为班级编写构造函数和破坏者来完成的。通过编写自己的
操作员新
和操作员删除
来完成微调内存管理。运算符重载的基本规则的第一个规则 - 不这样做 - 特别适用于过载
new
和delete
。这些操作员超负荷的唯一原因是 性能问题 和 记忆约束 ,在许多情况下,其他动作,就像使用算法的更改一样,与尝试调整内存管理相比,将提供更高的成本/增益比 更高的成本/增益比 。C ++标准库带有一组预定义的
新
和delete
运算符。最重要的是这些:前两个分配/deallocate对象的内存,后两个对象的后两个对象。如果您提供自己的版本,它们将 不是超载,而是替换 来自标准库中的版本。
如果您超载
操作员新
,即使您从不打算调用它,也应始终超载匹配运营商删除
。原因是,如果构造函数在新表达式评估期间投掷,则运行时系统将将内存返回到操作员删除
匹配操作员新
是呼吁分配内存以创建对象。如果您不提供匹配运算符删除
,则称为默认值,这几乎总是错误的。如果您过载
new
和删除
,则也应该考虑对数组变体过载。位置
新的
C ++允许新的和删除操作员进行其他参数。
所谓的安置新的新设备使您可以在传递给的特定地址创建一个对象:
标准库带有适当的新运算符和删除运算符的过载
:
运算符删除
从未调用,除非X的构造函数引发异常。您还可以使用其他参数过载
new
和delete
。与安置新的其他参数一样,这些参数也在关键字new
之后的括号中列出。仅出于历史原因,这种变体通常也称为新的位置,即使它们的论点不是将对象放置在特定地址上的原因。最常见的是特定于类的新和删除,
您需要微调内存管理,因为测量表明,经常创建和破坏特定类或一组相关类的实例,并且运行的默认存储器管理 - 针对一般绩效调整的时间系统在这种特定情况下效率低下。为了改善这一点,您可以为特定类超载新的和删除:
超载:因此,新的和删除的表现就像静态成员功能一样。对于
my_class
的对象,std :: size_t
参数将始终为sizef(my_class)
。但是,这些运算符也被要求用于 派生类的动态分配对象 ,在这种情况下,它可能大于此。全局新和删除
以超载全局新和删除,只需用我们自己的标准库的预定义运算符。但是,这很少需要做。
Overloading
new
anddelete
operatorsNote: This only deals with the syntax of overloading
new
anddelete
, not with the implementation of such overloaded operators. I think that the semantics of overloadingnew
anddelete
deserve their own FAQ, within the topic of operator overloading I can never do it justice.Basics
In C++, when you write a new expression like
new T(arg)
two things happen when this expression is evaluated: Firstoperator new
is invoked to obtain raw memory, and then the appropriate constructor ofT
is invoked to turn this raw memory into a valid object. Likewise, when you delete an object, first its destructor is called, and then the memory is returned tooperator delete
.C++ allows you to tune both of these operations: memory management and the construction/destruction of the object at the allocated memory. The latter is done by writing constructors and destructors for a class. Fine-tuning memory management is done by writing your own
operator new
andoperator delete
.The first of the basic rules of operator overloading – don’t do it – applies especially to overloading
new
anddelete
. Almost the only reasons to overload these operators are performance problems and memory constraints, and in many cases, other actions, like changes to the algorithms used, will provide a much higher cost/gain ratio than attempting to tweak memory management.The C++ standard library comes with a set of predefined
new
anddelete
operators. The most important ones are these:The first two allocate/deallocate memory for an object, the latter two for an array of objects. If you provide your own versions of these, they will not overload, but replace the ones from the standard library.
If you overload
operator new
, you should always also overload the matchingoperator delete
, even if you never intend to call it. The reason is that, if a constructor throws during the evaluation of a new expression, the run-time system will return the memory to theoperator delete
matching theoperator new
that was called to allocate the memory to create the object in. If you do not provide a matchingoperator delete
, the default one is called, which is almost always wrong.If you overload
new
anddelete
, you should consider overloading the array variants, too.Placement
new
C++ allows new and delete operators to take additional arguments.
So-called placement new allows you to create an object at a certain address which is passed to:
The standard library comes with the appropriate overloads of the new and delete operators for this:
Note that, in the example code for placement new given above,
operator delete
is never called, unless the constructor of X throws an exception.You can also overload
new
anddelete
with other arguments. As with the additional argument for placement new, these arguments are also listed within parentheses after the keywordnew
. Merely for historical reasons, such variants are often also called placement new, even if their arguments are not for placing an object at a specific address.Class-specific new and delete
Most commonly you will want to fine-tune memory management because measurement has shown that instances of a specific class, or of a group of related classes, are created and destroyed often and that the default memory management of the run-time system, tuned for general performance, deals inefficiently in this specific case. To improve this, you can overload new and delete for a specific class:
Overloaded thus, new and delete behave like static member functions. For objects of
my_class
, thestd::size_t
argument will always besizeof(my_class)
. However, these operators are also called for dynamically allocated objects of derived classes, in which case it might be greater than that.Global new and delete
To overload the global new and delete, simply replace the pre-defined operators of the standard library with our own. However, this rarely ever needs to be done.
为什么不能
操作员&lt;&lt;
std :: cout
或对文件是成员函数?假设您有:
鉴于您无法使用:
因为
operator&lt;&lt;&lt;
已重载作为foo
的成员函数,操作员的LHS必须是foo
对象。这意味着您将需要使用:这是非常不直觉的。
如果将其定义为非会员函数,
则可以使用:
它非常直观。
Why can't
operator<<
function for streaming objects tostd::cout
or to a file be a member function?Let's say you have:
Given that, you cannot use:
Since
operator<<
is overloaded as a member function ofFoo
, the LHS of the operator must be aFoo
object. Which means, you will be required to use:which is very non-intuitive.
If you define it as a non-member function,
You will be able to use:
which is very intuitive.
比较操作员,包括三路比较(C ++ 20)
有等效比较
==
和和! >,,
和关系比较>。
C ++ 20还引入了三向比较运算符
&lt; =&gt;
。x == y
x
和y
是等于满足 equalityComparable
(
std :: Unordered_map
使用)x
和y
是等于(可以用作
(x&lt) ; =&gt; y)== 0
,但通常不是)满足
std :: equality_comparable
x!= y
!(x == y)
>!(x == y)
x&lt; y
x
低于y
满足 sillthancomparable
(
std :: Set set
set ,std :: 等。
可能会满意
std
包裹在函子中时
(例如
std :: ranges :: Less
)x&gt; y
y&lt; x
(x&lt; =&gt; y)&gt; 0
x&lt; = y
!(x&lt; y)
对于强订单,x == y || x&lt; y
否则( x
(x&lt; =&gt; y)&gt; = 0
x&lt; =&gt; y
又名。 “太空飞船操作员”
满足
指南
==
,则定义!=
也(除非在C ++ 20中重写)。&lt;
,则定义&gt;
,&lt; =
,以及&gt; =
。&lt; =&gt;
而不是定义每个关系运算符。x == y
应等于!(x&lt; y)&amp;&amp; !(y&lt; x)
2)&lt;
方面不定义==
,即使您可以 3)1)否则,隐式转换将是不对称的,此等价不适用于这是由可读性,正确性和性能的动机。
==
有望将相同类型的隐式转换应用于两侧。2)
float
,但确实适用于int
和其他强订单的类型。 < br>3)
之前的常见成语
跳过C ++ 20部分,除非您对历史观点感兴趣。
所有运营商通常以非成员函数的形式实现,可能是“隐藏的朋友”>(
friend
s在类中定义该功能的位置)。以下所有代码示例都使用隐藏的朋友,因为无论如何需要比较私人成员,这是必要的。
注意:在C ++ 11中,所有这些通常都可以是
noexcept
和constexpr
。以
&lt的方式实现所有关系比较;如果我们有部分订购的成员(例如
float
),则无效。在这种情况下,必须以不同的方式编写
&lt; =
和&gt; =
。操作员&lt;
实现
操作员
并不那么简单,因为正确的词典比较不能简单地比较每个成员一次。{1,2}&lt; {3,0}
应该是正确的,即使2&lt; 0
是错误的。词典比较是实现A 严格的弱订单,这是,
std :: set
和算法之类的容器,例如std :: sort
。简而言之,a 严格的弱订购应该像&lt; 整数操作员,除了允许某些整数等效(例如,所有整数,
x&lt; y
是false)。如果
x!= y
等效于x&lt; y || y&lt; X
,一种更简单的方法:常见成语
多个成员的 ,您可以使用
std :: tie
以实现词典的比较:使用
std ::词典词法
对于数组成员。有些人使用宏或奇怪的重复模板模式(CRTP)保存委派
!=
,&gt;
,&gt; =
,和&lt; =
,或模仿C ++ 20的三路比较。也可以使用
std :: rel_ops
/a>(在C ++ 20中弃用)为委托!=
,&gt;
,&lt; =
,以及&gt; =
to&lt;
和==
在某些范围中的所有类型。默认比较(C ++ 20)
大量比较操作员只需比较一个类的每个成员。
如果是这样,则实施是纯样板,我们可以让编译器做到这一切:
注意:默认比较操作员需要是班级的
friend
s exship s,而最简单的完成方法是通过将它们定义为班级内的默认值。这使他们成为“隐藏的朋友”。另外,我们可以默认个人比较操作员。
如果我们要定义平等比较或仅关系比较,这将很有用:
请参阅
。
“ nofollow noreferrer”>默认 使用重写候选人。
因此,即使
&lt; =&gt;
未默认(这将实现 all 运算符),我们只需要实现==
和&lt; =&gt;
,并且所有其他比较都是根据这两个进行的。x == y
y == x
x!= y
!(x == y)
或!(y == x)
如果平等比较返回bool
x&lt; y
(x&lt; =&gt; y)&lt; 0
或0&lt; (y&lt; =&gt; x)
如果比较结果与零x&gt; y
(x&lt; =&gt; y)&gt; 0
或0&gt; (y&lt; =&gt; x)
如果...x&lt; = y
(x&lt; =&gt; y)&lt; = 0
或0&lt; =(y&lt; =&gt; x)
如果...x&gt; = y
(x&lt; =&gt; y)&gt; = 0
或0&gt; =(y&lt; =&gt; x)
如果...注意:
constexpr
andnoexcept /code>是可选的,但几乎可以始终应用于比较操作员。
注意: p1185r2:
&lt; =&gt; !==
,ax == y
表达式也将被重写为(x&lt; =&gt; y)== 0
,但这本来是效率低下的,因为它错过了短路的机会。三路比较操作员(C ++ 20)
注:它被称为“太空飞船操作员”。另请参见 spaceShip-operator 。
x&lt; =&gt; y
是结果告诉我们x
是否低于,大于,等同或无序的y
。这类似于
strcmp
等函数。比较类别
该运算符的结果既不是
bool
bool inint
,而是比较类别的值。std :: strong_ordering
int
少
,等于=等效
,更大
std :: feek_ordering
少
,等效
,更大
std :: partial_ordering
float
少
,等效
,更大
,无序
std :: strong_ordering
s可以转换为std :: feek_ordering
,可以将其转换为std :: partial_ordering
。这些类别的值可与(例如
(x&lt; =&gt; y)== 0
)相提并论,这与比较上面的
函数具有相似的含义。但是,
std :: partial_ordering ::无序
返回false以进行所有比较。1)没有基本类型的
x&lt; =&gt; y
在std :: feek_ordering
中结果。在实践中,强大和弱的顺序是可以互换的。参见 std :: stront_ordering and std and std and std :: feek_ordering。 -way比较通常违约,但可以手动实现:
如果
s
的所有成员都不相同,则拥有auto> auto
返回类型可能是一个问题,因为比较类别(&lt; =&gt;
的类型)可能有所不同,并且多个返回
语句的扣除不一致。在这种情况下,我们可以直接指定类别(在返回类型中),也可以使用
std :: common_comparison_category
:1) 辅助功能,例如
std :: is_neq
比较&lt; =&gt;
的结果为零。它们更清楚地表达了意图,但是您不必使用它们。
常见的成语
,我们可以让
std :: tie
弄清楚详细信息:使用
std ::词典词典_compare_three_way_way
数组成员。Comparison Operators, including Three-Way Comparison(C++20)
There are equality comparisons
==
and!=
,and relational comparisons
<
,>
,<=
,>=
.C++20 has also introduced the three-way comparison operator
<=>
.x == y
x
andy
are equalsatisfies EqualityComparable
(used by
std::unordered_map
)x
andy
are equal(can be implemented as
(x <=> y) == 0
,but usually isn't)
satisfies
std::equality_comparable
x != y
!(x == y)
!(x == y)
x < y
x
is lower thany
satisfies LessThanComparable
(used by
std::set
,std::sort
, etc.but requires strict weak ordering)
(x <=> y) < 0
may satisfy
std::strict_weak_ordering
when wrapped in a functor
(e.g.
std::ranges::less
)x > y
y < x
(x <=> y) > 0
x <= y
!(x < y)
for strong orderings,x == y || x < y
otherwise(x <=> y) <= 0
x >= y
y <= x
(x <=> y) >= 0
x <=> y
aka. "spaceship operator"
satisfies
std::three_way_comparable
Guidelines
==
, define!=
too (unless it is rewritten in C++20).<
, define>
,<=
, and>=
too.<=>
over defining each relational operator.x == y
should be equivalent to!(x < y) && !(y < x)
2)==
in terms of<
, even when you could 3)1) Otherwise, implicit conversions would be asymmetrical, and
==
is expected to apply the same kinds of implicit conversions to both sides.2) This equivalence does not apply to
float
, but does apply toint
and other strongly ordered types.3) This is motivated by readability, correctness, and performance.
Implementation and Common Idioms Prior to C++20
Skip ahead to the C++20 parts unless you're interested in a historical perspective.
All operators are typically implemented as non-member functions, possibly as hidden friends (
friend
s where the function is defined inside the class).All following code examples use hidden friends because this becomes necessary if you need to compare private members anyway.
Note: in C++11, all of these can typically be
noexcept
andconstexpr
.Implementing all relational comparisons in terms of
<
is not valid if we have a partially ordered member (e.g.float
).In that case,
<=
and>=
must be written differently.Further Notes on
operator<
The implementation of
operator<
is not so simple because a proper lexicographical comparison cannot simply compare each member once.{1, 2} < {3, 0}
should be true, even though2 < 0
is false.A lexicographical comparison is a simple way of implementing a strict weak ordering, which is needed for containers like
std::set
and algorithms likestd::sort
. In short, a strict weak ordering should behave like the<
operator for integers, except that some integers are allowed to be equivalent (e.g. for all even integers,x < y
is false).If
x != y
is equivalent tox < y || y < x
, a simpler approach is possible:Common Idioms
For multiple members, you can use
std::tie
to implement comparison lexicographically:Use
std::lexicographical_compare
for array members.Some people use macros or the curiously recurring template pattern (CRTP) to save the boilerplate of delegating
!=
,>
,>=
, and<=
, or to imitate C++20's three-way comparison.It is also possible to use
std::rel_ops
(deprecated in C++20) to delegate!=
,>
,<=
, and>=
to<
and==
for all types in some scope.Default Comparisons (C++20)
A substantial amount of comparison operators simply compare each member of a class.
If so, the implementation is pure boilerplate and we can let the compiler do it all:
Note: defaulted comparison operators need to be
friend
s of the class, and the easiest way to accomplish that is by defining them as defaulted inside the class. This makes them "hidden friends".Alternatively, we can default individual comparison operators.
This is useful if we want to define equality comparison, or only relational comparison:
See the cppreference article on default comparison.
Expression Rewriting (C++20)
In C++20, if a comparison operator isn't user-declared, the compiler will also try to use rewrite candidates.
Thanks to this, even if
<=>
isn't defaulted (which would implement all operators), we only have to implement==
and<=>
, and all other comparisons are rewritten in terms of these two.x == y
y == x
x != y
!(x == y)
or!(y == x)
if equality comparison returnsbool
x < y
(x <=> y) < 0
or0 < (y <=> x)
if comparison result is comparable to zerox > y
(x <=> y) > 0
or0 > (y <=> x)
if ...x <= y
(x <=> y) <= 0
or0 <= (y <=> x)
if ...x >= y
(x <=> y) >= 0
or0 >= (y <=> x)
if ...Note:
constexpr
andnoexcept
are optional, but can almost always be applied to comparison operators.Note: Before P1185R2:
<=> != ==
, ax == y
expression would also be rewritten as(x <=> y) == 0
, but this would have been inefficient because it misses short-circuiting opportunities.Three-Way Comparison Operator (C++20)
Note: it is colloquially called "spaceship operator". See also spaceship-operator.
The basic idea behind
x <=> y
is that the result tells us whetherx
is lower than, greater than, equivalent to, or unordered withy
.This is similar to functions like
strcmp
in C.Comparison Categories
The result of this operator is neither
bool
norint
, but a value of comparison category.std::strong_ordering
int
less
,equal = equivalent
,greater
std::weak_ordering
less
,equivalent
,greater
std::partial_ordering
float
less
,equivalent
,greater
,unordered
std::strong_ordering
s can be converted tostd::weak_ordering
, which can be converted tostd::partial_ordering
.Values of these categories are comparable to (e.g.
(x <=> y) == 0
) and this has similar meaning to thecompare
function above.However,
std::partial_ordering::unordered
returns false for all comparisons.1) There are no fundamental types for which
x <=> y
results instd::weak_ordering
. Strong and weak orderings are interchangeable in practice; see Practical meaning of std::strong_ordering and std::weak_ordering.Manual Implementation of Three-Way Comparison
Three-way comparison is often defaulted, but could be implemented manually like:
If all members of
S
weren't the same type, having anauto
return type could be an issue because the comparison category (type of<=>
) could be different and the multiplereturn
statements would have inconsistent deduction.In that case, we could either specify the category directly (in the return type), or we could obtain it with
std::common_comparison_category
:1) Helper functions like
std::is_neq
compare the result of<=>
to zero.They express intent more clearly, but you don't have to use them.
Common Idioms
Alternatively, we can let
std::tie
figure out the details:Use
std::lexicographical_compare_three_way
for array members.规范功能签名的摘要
许多操作员超载几乎可以返回任何内容。例如,没有什么可以阻止您在
operator ==
中返回void
。但是,这些签名中只有少数是 canonical ,这意味着您通常会以这种方式写下它们,并且可以用
= default
明确地默认此类操作员。分配运算符
具有
=默认值=默认值;
是可能的,但是您也可以手动实现作业。移动分配几乎总是
noexcept
,尽管它不是强制性的。比较操作员
参见此答案比较。
算术运算符
也可以按值乘以二进制运算符的左操作员,但这不建议这样做,因为它使签名不对称并抑制编译器的优化。
钻头运算
符流插入和提取
功能调用操作员
下标子操作员
注意
operator []
自C ++ 23以来可以接受多个参数。成员访问操作员
指针到会员操作员
地址运算符
逻辑操作员
注意,这些操作员不会返回
bool
,因为它们仅在x
已经是一种逻辑类型时才有意义类似于bool
。用户定义的转换
Coroutine等待
逗号操作员
分配功能
Summary of Canonical Function Signatures
Many operator overloads can return pretty much anything. For example, nothing stops you from returning
void
inoperator==
.However, only a few of these signatures are canonical, which means that you would normally write them that way, and that such an operator can be explicitly defaulted with
= default
.Assignment Operators
Explicit defaulting with
= default;
is possible, but you can also implement assignment manually.Move assignment is almost always
noexcept
, although it isn't mandatory.Comparison Operators
See this answer for more information on when and how to default/implement comparisons.
Arithmetic Operators
It is also possible to take the left operator of binary operators by value, but this is not recommended because it makes the signature asymmetric and inhibits compiler optimizations.
Bitwise Operators
Stream Insertion and Extraction
Function Call Operator
Subscript Operator
Note that
operator[]
can accept multiple parameters since C++23.Member Access Operators
Pointer-to-Member Operator
Address-of Operator
Logical Operators
Note that these don't return
bool
because they only make sense ifX
is already a logical type that similar tobool
.User-defined Conversions
Coroutine Await
Comma Operator
Allocation Functions
简短而简单,我将提到一些观点,过去一周我学习Python和C ++,OOP和其他事物,因此,这是:
< < a href =“ https://en.wiktionary.org/wiki/arity#noun” rel =“ nofollow noreferrer”> arity bed bed Is!
超载运算符只能有一个默认参数,该函数调用操作员无法休息。
只有内置的操作员才能超载,其余的不能!
有关更多信息,您可以参考 操作员过载规则 ,将您重定向到
Making it short and simple, I'll be referring to some points, which I had come over the past week as I was learning Python and C++, OOP and other things, so it goes as follows:
The arity of the operator can not be modified further than to what it is!
Overloaded operators can only have one default argument which the function call operator rest it cannot.
Only built-in operators can be overloaded, and the rest can't!
For more information, you can refer to Rules for operator overloading, which redirects you to the documentation provided by GeeksforGeeks.