C++ 运算符重载的基本规则和习惯用法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4421706/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are the basic rules and idioms for operator overloading?
提问by sbi
Note: The answers were given in a specific order, but since many users sort answers according to votes, rather than the time they were given, here's an index of the answersin the order in which they make most sense:
注意:答案是按特定顺序给出的,但由于许多用户根据投票而不是给出时间对答案进行排序,以下是按照最有意义的顺序列出答案的索引:
- The General Syntax of operator overloading in C++
- The Three Basic Rules of Operator Overloading in C++
- The Decision between Member and Non-member
- Common operators to overload
- Assignment Operator
- Input and Output Operators
- Function call operator
- Comparison operators
- Arithmetic Operators
- Array Subscripting
- Operators for Pointer-like Types
- Conversion Operators
- Overloading new and delete
- C++中运算符重载的一般语法
- C++中运算符重载的三个基本规则
- 会员与非会员的决定
- 常用运算符重载
- 赋值运算符
- 输入和输出运算符
- 函数调用运算符
- 比较运算符
- 算术运算符
- 数组下标
- 类指针类型的运算符
- 转换运算符
- 重载新建和删除
(Note: This is meant to be an entry to Stack Overflow's C++ FAQ. If you want to critique the idea of providing an FAQ in this form, then the posting on meta that started all thiswould be the place to do that. Answers to that question are monitored in the C++ chatroom, where the FAQ idea started out in the first place, so your answer is very likely to get read by those who came up with the idea.)
(注意:这是Stack Overflow 的 C++ FAQ 的一个条目。如果你想批评以这种形式提供 FAQ 的想法,那么开始所有这一切的 meta 上的帖子将是这样做的地方。答案该问题在C++ 聊天室中受到监控,FAQ 的想法首先在这里开始,因此您的答案很可能会被提出该想法的人阅读。)
回答by sbi
Common operators to overload
常用运算符重载
Most of the work in overloading operators is boiler-plate 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 boiler-plate code right. If you fail, either your operator's code won't compile or 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:
关于分配有很多话要说。不过,大部分内容已经在GMan 著名的 Copy-And-Swap FAQ中说到了,所以我在这里跳过大部分内容,只列出完美的赋值运算符以供参考:
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
Bitshift Operators (used for Stream I/O)
Bitshift 运算符(用于流 I/O)
The bitshift 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. For guidance overloading as bit-manipulation operators, see the section below on Binary Arithmetic Operators. For implementing your own custom format and parsing logic when your object is used with iostreams, continue.
位移操作符<<
和>>
虽然仍然用于硬件接口以实现它们从 C 继承的位操作函数,但在大多数应用程序中作为重载的流输入和输出操作符已经变得更加普遍。有关作为位操作运算符重载的指导,请参阅下面有关二进制算术运算符的部分。当您的对象与 iostreams 一起使用时,要实现您自己的自定义格式和解析逻辑,请继续。
The stream operators, among the most commonly overloaded operators, are binary infix operators for which the syntax specifies no restriction on whether they should be members or non-members. Since they change their left argument (they alter the stream's state), they should, according to the rules of thumb, be implemented as members of their left operand's type. However, their left operands are streams from the standard library, and while most of the stream output and input operators defined by the standard library are indeed defined as members of the stream classes, when you implement output and input operations for your own types, you cannot change the standard library's stream types. That's why you need to implement these operators for your own types as non-member functions. The canonical forms of the two are these:
流操作符是最常见的重载操作符之一,是二元中缀操作符,其语法对它们是成员还是非成员没有限制。因为它们改变了它们的左参数(它们改变了流的状态),所以根据经验法则,它们应该被实现为它们的左操作数类型的成员。然而,它们的左操作数是来自标准库的流,虽然标准库定义的大多数流输出和输入操作符确实被定义为流类的成员,当你为自己的类型实现输出和输入操作时,你无法更改标准库的流类型。这就是为什么您需要为您自己的类型将这些运算符实现为非成员函数的原因。两者的规范形式是:
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
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.
在实现时operator>>
,只有在读取本身成功时才需要手动设置流的状态,但结果不是预期的。
Function call operator
函数调用运算符
The function call operator, used to create function objects, also known as functors, must be defined as a memberfunction, 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.
函数调用运算符,用于创建函数对象,也称为函子,必须定义为成员函数,因此它始终具有this
成员函数的隐式参数。除此之外,它可以被重载以接受任意数量的附加参数,包括零。
Here's an example of the syntax:
下面是一个语法示例:
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
Usage:
用法:
foo f;
int a = f("hello");
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.
在整个 C++ 标准库中,函数对象总是被复制。因此,您自己的函数对象复制起来应该很便宜。如果函数对象绝对需要使用复制成本很高的数据,最好将该数据存储在其他地方并让函数对象引用它。
Comparison operators
比较运算符
The binary infix comparison operators should, according to the rules of thumb, be implemented as non-member functions1. The unary prefix negation !
should (according to the same rules) be implemented as a member function. (but it is usually not a good idea to overload it.)
根据经验法则,二进制中缀比较运算符应该实现为非成员函数1。一元前缀否定!
应该(根据相同的规则)作为成员函数实现。(但超载通常不是一个好主意。)
The standard library's algorithms (e.g. std::sort()
) and types (e.g. std::map
) will always only expect operator<
to be present. However, the users of your type will expect all the other operators to be present, too, so if you define operator<
, be sure to follow the third fundamental rule of operator overloading and also define all the other boolean comparison operators. The canonical way to implement them is this:
标准库的算法(例如std::sort()
)和类型(例如std::map
)将始终只期望operator<
出现。但是,您类型的用户也希望所有其他运算符都存在,因此如果您定义operator<
,请务必遵循运算符重载的第三个基本规则,并定义所有其他布尔比较运算符。实现它们的规范方法是这样的:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
The important thing to note here is that only two of these operators actually do anything, the others are just forwarding their arguments to either of these two to do the actual work.
这里要注意的重要一点是,这些操作符中只有两个真正做任何事情,其他操作符只是将它们的参数转发给这两个操作符中的任何一个来执行实际工作。
The syntax for overloading the remaining binary boolean operators (||
, &&
) follows the rules of the comparison operators. However, it is veryunlikely that you would find a reasonable use case for these2.
重载其余二元布尔运算符 ( ||
, &&
)的语法遵循比较运算符的规则。然而,这是非常不可能的,你会发现这些合理的使用案例2。
1As with all rules of thumb, sometimes there might be reasons to break this one, too. If so, do not forget that the left-hand operand of the binary comparison operators, which for member functions will be *this
, needs to be const
, too. So a comparison operator implemented as a member function would have to have this signature:
1与所有经验法则一样,有时也可能有理由打破这一法则。如果是这样,请不要忘记二元比较运算符的左侧操作数(对于成员函数将是 )*this
,也需要是const
。因此,作为成员函数实现的比较运算符必须具有以下签名:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(Note the const
at the end.)
(注意const
最后。)
2It 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.
2需要注意的是内置版本||
和&&
使用快捷语义。而用户定义的(因为它们是方法调用的语法糖)不使用快捷语义。用户会期望这些操作符具有快捷语义,并且他们的代码可能依赖于它,因此强烈建议永远不要定义它们。
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:
一元递增和递减运算符有前缀和后缀两种形式。为了区分一个和另一个,后缀变体需要一个额外的虚拟 int 参数。如果您重载 increment 或 decrement,请确保始终实现前缀和后缀版本。这是 increment 的规范实现,decrement 遵循相同的规则:
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
Note that the postfix variant is implemented in terms of prefix. Also note that postfix does an extra copy.2
请注意,后缀变体是根据前缀实现的。还要注意 postfix 做了一个额外的副本。2
Overloading unary minus and plus is not very common and probably best avoided. If needed, they should probably be overloaded as member functions.
重载一元减号和加号不是很常见,最好避免。如果需要,它们可能应该作为成员函数重载。
2Also 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 when i
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.
2另请注意,后缀变体做了更多工作,因此使用效率低于前缀变体。这是通常更喜欢前缀增量而不是后缀增量的一个很好的理由。虽然编译器通常可以优化内置类型的后缀增量的额外工作,但他们可能无法为用户定义的类型做同样的事情(这可能就像列表迭代器一样无辜)。一旦习惯了 do i++
,++i
当i
它不是内置类型时就很难记住要执行的操作(而且在更改类型时必须更改代码),因此最好养成始终使用的习惯使用前缀增量,除非明确需要后缀。
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.
对于二元算术运算符,不要忘记遵守运算符重载的第三条基本规则:如果你提供+
,也提供+=
,如果你提供-
,不要省略-=
等等。据说 Andrew Koenig 是第一个观察到复合赋值的人运算符可以用作其非复合对应物的基础。也就是说,操作符+
是根据来实现的+=
,-
是根据-=
等来实现的。
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:
根据我们的经验法则,+
它的同伴应该是非成员,而它们的复合赋值对应物(+=
等),改变它们的左参数,应该是成员。这是+=
and的示例代码+
;其他二元算术运算符应该以相同的方式实现:
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=
returns its result per reference, while operator+
returns a copy of its result. Of course, returning a reference is usually more efficient than returning a copy, but in the case of operator+
, there is no way around the copying. When you write a + b
, you expect the result to be a new value, which is why operator+
has to return a new value.3Also note that operator+
takes its left operand by copyrather than by const reference. The reason for this is the same as the reason giving for operator=
taking its argument per copy.
operator+=
每个引用返回其结果,同时operator+
返回其结果的副本。当然,返回引用通常比返回副本更有效,但在 的情况下operator+
,复制是没有办法的。当您编写 时a + b
,您希望结果是一个新值,这就是operator+
必须返回新值的原因。3另请注意,通过复制而不是通过常量引用operator+
获取其左操作数。这样做的原因与给出每个副本的参数的原因相同。operator=
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.
位操作运算符~
&
|
^
<<
>>
应以与算术运算符相同的方式实现。但是,(除了重载<<
以及>>
输出和输入)重载这些的合理用例很少。
3Again, the lesson to be taken from this is that a += b
is, in general, more efficient than a + b
and should be preferred if possible.
3同样,从中吸取的教训a += b
是,一般来说,它比a + b
可能的情况下更有效,并且应该是首选。
Array Subscripting
数组下标
The array 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:
数组下标运算符是一个二元运算符,必须作为类成员实现。它用于允许通过键访问其数据元素的类容器类型。提供这些的规范形式是这样的:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
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.
除非您不希望您的类的用户能够更改返回的数据元素operator[]
(在这种情况下您可以省略非常量变体),您应该始终提供运算符的两个变体。
If value_type is known to refer to a built-in type, the const variant of the operator should better return a copy instead of a const reference:
如果已知 value_type 引用内置类型,则运算符的 const 变体应该更好地返回副本而不是 const 引用:
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
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 ->
:
要定义您自己的迭代器或智能指针,您必须重载一元前缀解引用运算符*
和二元中缀指针成员访问运算符->
:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
Note that these, too, will almost always need both a const and a non-const version.
For the ->
operator, if value_type
is of class
(or struct
or union
) type, another operator->()
is called recursively, until an operator->()
returns a value of non-class type.
请注意,这些也几乎总是需要常量和非常量版本。对于->
运算符,如果value_type
是class
(or struct
or union
) 类型,operator->()
则递归调用另一个,直到operator->()
返回非类类型的值。
The unary address-of operator should never be overloaded.
一元地址运算符永远不应该被重载。
For operator->*()
see this question. It's rarely used and thus rarely ever overloaded. In fact, even iterators do not overload it.
对于operator->*()
看到这个问题。它很少使用,因此很少超载。事实上,即使是迭代器也不会重载它。
Continue to Conversion Operators
继续转换运算符
回答by sbi
The Three Basic Rules of Operator Overloading in C++
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.
当谈到 C++ 中的运算符重载时,您应该遵循三个基本规则。与所有此类规则一样,确实存在例外情况。有时人们偏离了它们,结果不是糟糕的代码,但这种积极的偏差很少见。至少,我所看到的 100 个这样的偏差中有 99 个是不合理的。但是,它也可能是 1000 个中的 999 个。因此,您最好遵守以下规则。
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 otherand to other operations. If your type supportsa + 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.
每当运算符的含义不明显且无可争议时,不应对其进行重载。相反,提供一个具有精心选择的名称的函数。
基本上,重载运算符的首要规则的核心是:不要这样做。这可能看起来很奇怪,因为有很多关于运算符重载的知识,所以很多文章、书籍章节和其他文本都涉及所有这些。但是,尽管有这个看似显而易见的证据,但只有少数情况下运算符重载是合适的. 原因是实际上很难理解运算符应用背后的语义,除非运算符在应用领域的使用是众所周知且无可争议的。与流行的看法相反,情况几乎从未如此。始终坚持操作符众所周知的语义。
C++ 对重载运算符的语义没有限制。您的编译器很乐意接受实现二元+
运算符以从其右操作数中减去的代码。但是,这种运算符的用户永远不会怀疑要从a + b
中减去的表达式。当然,这假设应用领域中的运算符的语义是无可争议的。a
b
始终提供一组相关操作中的所有内容。
操作符彼此相关并与其他操作相关。如果您的类型支持a + b
,用户也希望能够调用a += b
。如果它支持 prefix increment++a
,他们将期望也a++
能工作。如果他们能够检查是否a < b
,他们肯定会期望也能够检查是否a > b
。如果他们可以复制构造您的类型,他们希望分配也能正常工作。
Continue to The Decision between Member and Non-member.
继续会员和非会员之间的决定。
回答by sbi
The General Syntax of operator overloading in C++
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.
您不能更改 C++ 中内置类型的运算符的含义,只能为用户定义的类型1重载运算符。也就是说,至少一个操作数必须是用户定义的类型。与其他重载函数一样,运算符只能为特定参数集重载一次。
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++, ?:
并非所有运算符都可以在 C++ 中重载。不能重载的运算符包括:.
::
sizeof
typeid
.*
以及 C++ 中唯一的三元运算符,?:
Among the operators that can be overloaded in C++ are these:
在 C++ 中可以重载的运算符包括:
- arithmetic operators:
+
-
*
/
%
and+=
-=
*=
/=
%=
(all binary infix);+
-
(unary prefix);++
--
(unary prefix and postfix) - bit manipulation:
&
|
^
<<
>>
and&=
|=
^=
<<=
>>=
(all binary infix);~
(unary prefix) - boolean algebra:
==
!=
<
>
<=
>=
||
&&
(all binary infix);!
(unary prefix) - memory management:
new
new[]
delete
delete[]
- implicit conversion operators
- miscellany:
=
[]
->
->*
,
(all binary infix);*
&
(all unary prefix)()
(function call, n-ary infix)
- 算术运算符:
+
-
*
/
%
和+=
-=
*=
/=
%=
(所有二进制中缀);+
-
(一元前缀);++
--
(一元前缀和后缀) - 位操作:
&
|
^
<<
>>
和&=
|=
^=
<<=
>>=
(所有二进制中缀);~
(一元前缀) - 布尔代数:(
==
!=
<
>
<=
>=
||
&&
所有二进制中缀);!
(一元前缀) - 内存管理:
new
new[]
delete
delete[]
- 隐式转换运算符
- 杂项:(
=
[]
->
->*
,
所有二进制中缀);*
&
(所有一元前缀)()
(函数调用,n 元中缀)
However, the fact that you canoverload all of these does not mean you shoulddo so. See the basic rules of operator overloading.
但是,您可以使所有这些过载的事实并不意味着您应该这样做。请参阅运算符重载的基本规则。
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 typeor as non-member functions. Whether you are free to choose or bound to use either one depends on several criteria.2A unary operator @
3, applied to an object x, is invoked either as operator@(x)
or as x.operator@()
. A binary infix operator @
, applied to the objects x
and y
, is called either as operator@(x,y)
or as x.operator@(y)
.4
在 C++ 中,运算符以具有特殊名称的函数的形式重载。与其他函数一样,重载运算符通常可以作为其左操作数类型的成员函数或作为非成员函数来实现。您是否可以自由选择或必须使用其中任何一种取决于几个标准。2应用于对象 x 的一元运算符@
3被调用为 asoperator@(x)
或 as x.operator@()
。@
应用于对象x
and 的二元中缀运算符y
称为 asoperator@(x,y)
或 as x.operator@(y)
。4
Operators that are implemented as non-member functions are sometimes friend of their operand's type.
实现为非成员函数的运算符有时是其操作数类型的朋友。
1The 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.
1术语“用户定义”可能有点误导。C++ 区分内置类型和用户定义类型。前者属于例如 int、char 和 double;后者属于所有 struct、class、union 和 enum 类型,包括来自标准库的类型,即使它们不是由用户定义的。
2This is covered in a later partof this FAQ.
2这是覆盖在后面的部分这个常见问题。
3The @
is not a valid operator in C++ which is why I use it as a placeholder.
3 The @
is not a valid operator in C++ 这就是我将它用作占位符的原因。
4The only ternary operator in C++ cannot be overloaded and the only n-ary operator must always be implemented as a member function.
4 C++ 中唯一的三元运算符不能重载,唯一的 n 元运算符必须始终作为成员函数实现。
Continue to The Three Basic Rules of Operator Overloading in C++.
继续阅读C++ 中运算符重载的三个基本规则。
回答by sbi
The Decision between Member and Non-member
会员与非会员的决定
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.
二元运算符=
(赋值)、[]
(数组订阅)、->
(成员访问)以及 n 元()
(函数调用)运算符必须始终实现为成员函数,因为语言的语法要求它们这样做。
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.
其他运算符可以作为成员或非成员实现。但是,其中一些通常必须作为非成员函数来实现,因为您无法修改它们的左操作数。其中最突出的是输入和输出运算符<<
and >>
,其左操作数是标准库中的流类,您无法更改。
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 thumbto decide:
对于您必须选择将它们实现为成员函数或非成员函数的所有运算符,请使用以下经验法则来决定:
- If it is a unary operator, implement it as a memberfunction.
- If a binary operator treats both operands equally(it leaves them unchanged), implement this operator as a non-memberfunction.
- If a binary operator does nottreat both of its operands equally(usually it will change its left operand), it might be useful to make it a memberfunction of its left operand's type, if it has to access the operand's private parts.
- 如果是一元运算符,则将其实现为成员函数。
- 如果二元运算符平等对待两个操作数(它使它们保持不变),则将此运算符实现为非成员函数。
- 如果二元运算并没有把它的两个操作数相等(通常它会改变其左操作数),它可能是使它成为一个有用的成员其左操作数的类型的函数,如果它有访问操作数的私处。
Of course, as with all rules of thumb, there are exceptions. If you have a type
当然,与所有经验法则一样,也有例外。如果你有一个类型
enum Month {Jan, Feb, ..., Nov, Dec}
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.
并且您想为其重载增量和减量运算符,您不能将其作为成员函数来执行,因为在 C++ 中,枚举类型不能具有成员函数。所以你必须把它作为一个自由函数重载。和operator<()
嵌套类模板中的类模板是写起来更简单,当作为类定义的成员函数内联方式完成阅读。但这些确实是罕见的例外。
(However, ifyou make an exception, do not forget the issue of const
-ness for the operand that, for member functions, becomes the implicit this
argument. If the operator as a non-member function would take its left-most argument as a const
reference, the same operator as a member function needs to have a const
at the end to make *this
a const
reference.)
(但是,如果您例外,请不要忘记const
操作数的-ness问题,对于成员函数,该问题将成为隐式this
参数。如果运算符作为非成员函数将其最左侧的参数作为const
引用中,相同的操作者作为成员函数需要具有const
在端部,使*this
一const
参考)。
Continue to Common operators to overload.
继续通用运算符重载。
回答by JKor
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.
在 C++ 中,您可以创建转换运算符,这些运算符允许编译器在您的类型和其他定义的类型之间进行转换。有两种类型的转换运算符,隐式和显式。
Implicit Conversion Operators (C++98/C++03 and C++11)
隐式转换运算符(C++98/C++03 和 C++11)
An implicit conversion operator allows the compiler to implicitly convert (like the conversion between int
and long
) the value of a user-defined type to some other type.
隐式转换运算符允许编译器将用户定义类型的值隐式转换(如在int
和之间的转换long
)到其他类型。
The following is a simple class with an implicit conversion operator:
下面是一个带有隐式转换运算符的简单类:
class my_string {
public:
operator const char*() const {return data_;} // This is the conversion operator
private:
const char* data_;
};
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.
隐式转换运算符,如单参数构造函数,是用户定义的转换。当尝试匹配对重载函数的调用时,编译器将授予一个用户定义的转换。
void f(const char*);
my_string str;
f(str); // same as f( str.operator const char*() )
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 because my_string()
is not an lvalue, so the first does not match:
起初这似乎很有帮助,但问题在于隐式转换甚至在不期望的时候开始。在下面的代码中,void f(const char*)
将被调用,因为my_string()
不是左值,所以第一个不匹配:
void f(my_string&);
void f(const char*);
f(my_string());
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.
初学者很容易弄错,甚至有经验的 C++ 程序员有时也会感到惊讶,因为编译器选择了他们没有怀疑的重载。这些问题可以通过显式转换运算符来缓解。
Explicit Conversion Operators (C++11)
显式转换运算符 (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:
与隐式转换运算符不同,显式转换运算符永远不会在您不希望它们启动时启动。下面是一个带有显式转换运算符的简单类:
class my_string {
public:
explicit operator const char*() const {return data_;}
private:
const char* data_;
};
Notice the explicit
. Now when you try to execute the unexpected code from the implicit conversion operators, you get a compiler error:
请注意explicit
. 现在,当您尝试从隐式转换运算符执行意外代码时,您会收到编译器错误:
prog.cpp: In function ‘int main()': prog.cpp:15:18: error: no matching function for call to ‘f(my_string)' prog.cpp:15:18: note: candidates are: prog.cpp:11:10: note: void f(my_string&) prog.cpp:11:10: note: no known conversion for argument 1 from ‘my_string' to ‘my_string&' prog.cpp:12:10: note: void f(const char*) prog.cpp:12:10: note: no known conversion for argument 1 from ‘my_string' to ‘const char*'
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)
).
要调用显式转换运算符,您必须使用static_cast
C 样式转换或构造函数样式转换(即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 to bool
(a compiler is allowed to do 2 implicit conversions at a time, but only 1 user-defined conversion at max).
但是,有一个例外:允许编译器隐式转换为bool
. 另外,编译器在转换为之后不允许再做一次隐式转换bool
(一个编译器一次可以做2次隐式转换,但最多只能做1次用户自定义转换)。
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.
因为编译器不会强制bool
转换"past" ,显式转换运算符现在不再需要Safe Bool idiom。例如,C++11 之前的智能指针使用 Safe Bool 惯用语来防止转换为整型。在 C++11 中,智能指针改为使用显式运算符,因为在将类型显式转换为 bool 之后,不允许编译器隐式转换为整型。
Continue to Overloading new
and delete
.
继续重载new
和delete
。
回答by sbi
Overloading new
and delete
重载new
和delete
Note:This only deals with the syntaxof overloading new
and delete
, not with the implementationof such overloaded operators. I think that the semantics of overloading new
and delete
deserve their own FAQ, within the topic of operator overloading I can never do it justice.
注意:这仅涉及重载and的语法,而不涉及此类重载运算符的实现。我认为,超载的语义和应该拥有自己的常见问题,运营商的主题中超载我永远不能做到公正。new
delete
new
delete
Basics
基本
In C++, when you write a new expressionlike new T(arg)
two things happen when this expression is evaluated: First operator new
is invoked to obtain raw memory, and then the appropriate constructor of T
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 to operator 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
and operator delete
.
在C ++中,当你写一个新的表达像new T(arg)
两件事情发生时,该表达式计算:首先operator new
被调用,以获得原始内存,然后适当的构造函数T
被调用来把这个原始内存为有效的对象。同样,当你删除一个对象时,首先调用它的析构函数,然后将内存返回给operator delete
。
C++ 允许您调整这两个操作:内存管理和在分配的内存中对象的构造/销毁。后者是通过为类编写构造函数和析构函数来完成的。微调内存管理是通过编写自己的operator new
和operator delete
.
The first of the basic rules of operator overloading – don't do it– applies especially to overloading new
and delete
. Almost the only reasons to overload these operators are performance problemsand memory constraints, and in many cases, other actions, like changes to the algorithmsused, will provide a much higher cost/gain ratiothan attempting to tweak memory management.
运算符重载的第一条基本规则——不要这样做——尤其适用于重载new
和delete
。几乎使这些运算符过载的唯一原因是性能问题和内存限制,并且在许多情况下,与尝试调整内存管理相比,其他操作(例如更改所使用的算法)将提供更高的成本/收益比。
The C++ standard library comes with a set of predefined new
and delete
operators. The most important ones are these:
C++ 标准库带有一组预定义的new
和delete
运算符。最重要的是这些:
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
void* operator new[](std::size_t) throw(std::bad_alloc);
void operator delete[](void*) throw();
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 replacethe ones from the standard library.
If you overload operator new
, you should always also overload the matching operator 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 the operator delete
matching the operator new
that was called to allocate the memory to create the object in. If you do not provide a matching operator delete
, the default one is called, which is almost always wrong.
If you overload new
and delete
, you should consider overloading the array variants, too.
前两个为对象分配/释放内存,后两个为对象数组。如果您提供自己的这些版本,它们不会重载,而是替换标准库中的版本。
如果您重载operator new
,您也应该始终重载匹配的operator delete
,即使您从未打算调用它。其原因在于,如果一个构造一个新的表达式的计算过程中抛出,运行时系统将内存返回operator delete
匹配operator new
的是被称为分配给创建的对象的内存。如果你不提供匹配operator delete
,调用默认的,这几乎总是错误的。
如果你重载new
and delete
,你也应该考虑重载数组变体。
Placement new
放置 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:
C++ 允许 new 和 delete 运算符接受额外的参数。
所谓的placement new 允许您在某个地址创建一个对象,该对象被传递到:
class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{
X* p = new(buffer) X(/*...*/);
// ...
p->~X(); // call destructor
}
The standard library comes with the appropriate overloads of the new and delete operators for this:
标准库为此提供了适当的 new 和 delete 运算符重载:
void* operator new(std::size_t,void* p) throw(std::bad_alloc);
void operator delete(void* p,void*) throw();
void* operator new[](std::size_t,void* p) throw(std::bad_alloc);
void operator delete[](void* p,void*) throw();
Note that, in the example code for placement new given above, operator delete
is never called, unless the constructor of X throws an exception.
请注意,在上面给出的放置 new 示例代码中,operator delete
永远不会调用,除非 X 的构造函数抛出异常。
You can also overload new
and delete
with other arguments. As with the additional argument for placement new, these arguments are also listed within parentheses after the keyword new
. 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.
您还可以使用其他参数重载new
和delete
。与放置 new 的附加参数一样,这些参数也列在关键字 之后的括号内new
。仅出于历史原因,此类变体通常也称为新放置,即使它们的论据不是为了将对象放置在特定地址。
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:
最常见的情况是,您需要微调内存管理,因为测量表明,特定类或一组相关类的实例经常被创建和销毁,并且运行时系统的默认内存管理已针对一般性能,在这种特定情况下处理效率低下。为了改善这一点,您可以为特定类重载 new 和 delete :
class my_class {
public:
// ...
void* operator new();
void operator delete(void*,std::size_t);
void* operator new[](size_t);
void operator delete[](void*,std::size_t);
// ...
};
Overloaded thus, new and delete behave like static member functions. For objects of my_class
, the std::size_t
argument will always be sizeof(my_class)
. However, these operators are also called for dynamically allocated objects of derived classes, in which case it might be greater than that.
因此重载时,new 和delete 的行为类似于静态成员函数。对于 的对象my_class
,std::size_t
参数将始终是sizeof(my_class)
。但是,这些运算符也被用于派生类的动态分配对象,在这种情况下,它可能大于此值。
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.
要重载全局 new 和 delete,只需用我们自己的替换标准库的预定义运算符。但是,很少需要这样做。
回答by R Sahu
Why can't operator<<
function for streaming objects to std::cout
or to a file be a member function?
为什么将operator<<
对象流式传输std::cout
到文件或流式传输到文件的函数不能成为成员函数?
Let's say you have:
假设你有:
struct Foo
{
int a;
double b;
std::ostream& operator<<(std::ostream& out) const
{
return out << a << " " << b;
}
};
Given that, you cannot use:
鉴于此,您不能使用:
Foo f = {10, 20.0};
std::cout << f;
Since operator<<
is overloaded as a member function of Foo
, the LHS of the operator must be a Foo
object. Which means, you will be required to use:
由于operator<<
作为 的成员函数重载Foo
,因此运算符的 LHS 必须是一个Foo
对象。这意味着,您将需要使用:
Foo f = {10, 20.0};
f << std::cout
which is very non-intuitive.
这是非常不直观的。
If you define it as a non-member function,
如果将其定义为非成员函数,
struct Foo
{
int a;
double b;
};
std::ostream& operator<<(std::ostream& out, Foo const& f)
{
return out << f.a << " " << f.b;
}
You will be able to use:
您将能够使用:
Foo f = {10, 20.0};
std::cout << f;
which is very intuitive.
这是非常直观的。