为什么一些操作符只能作为成员函数被重载,其他的作为朋友函数,其余的都作为成员函数?
为什么一些运算符只能作为成员函数被重载,其他的则作为非成员的“自由”函数,其余的都作为成员函数?
这些背后的理由是什么?
如何记住哪些运算符可以被重载为(成员,自由,或两者)?
这个问题列出了三类运营商。 把它们放在一个列表中,我认为,理解为什么less数操作员被限制在可以超载的地方:
-
必须作为成员超载的运算符。 这些是相当less的:
- 赋值
operator=()
。 允许非成员分配似乎为操作员劫持分配打开了大门,例如,通过超载为不同版本的const
资格。 鉴于赋值运算符是相当基本的,似乎是不可取的。 - 函数调用
operator()()
。 函数调用和重载规则是如此的复杂。 通过允许非成员函数调用操作符来进一步使规则复杂化似乎是不明智的。 - 下标
operator[]()
。 使用有趣的索引types似乎会干扰访问操作员。 尽pipe劫持重载的风险很小,但是编写高度不明显的代码似乎并没有太大的好处,但却有一些有趣的潜力。 - 类成员访问
operator->()
。 副手,我看不出任何滥用这个运算符超载非会员。 另一方面,我也看不到任何。 而且,class级成员接入运营商有相当特殊的规则,玩弄干扰这些潜在的超载似乎是不必要的复杂性。
虽然可以想象将这些成员中的每一个都重载为非成员(特别是对数组/指针起作用的下标运算符,并且这些运算符可以位于调用的任一侧),但是如果例如可以劫持任务似乎是令人惊讶的通过非成员超载比哪一个成员指派更好匹配。 这些运算符也相当不对称:您通常不希望支持包含这些运算符的expression式的双方转换。
这就是说,例如,对于一个lambdaexpression式库,如果有可能重载所有这些运算符,我认为不存在阻止这些运算符被重载的内在技术原因。
- 赋值
-
必须作为非成员函数重载的运算符。
- 用户定义的文字
operator"" name()
这个操作员有点奇怪,可以说不是真正的操作员。 在任何情况下,都没有对象可以调用这个成员来定义成员:用户定义文字的左边参数总是内置types。
- 用户定义的文字
-
在问题中没有提到,但也有运营商根本不能超载:
- 成员select器
.
- 指向成员对象的访问操作符
.*
- 范围操作符
::
- 三元操作符
?:
这四个运营商被认为是根本性的,根本无法干预。 虽然有一个允许重载
operator.()
的提议,但在某些时候没有强大的支持(主要用例是智能引用)。 尽pipe当然也有一些可以想象的情况,那就是超载这些操作符也不错。 - 成员select器
-
可以作为成员或非成员重载的运算符。 这是运营商的大部分:
- 前后递增/递减
operator++()
,operator--()
,operator++(int)
,operator--(int)
- [一元]取消引用
operator*()
- 运算
operator&()
的[一元]地址 - [一元]符号
operator+()
,operator-()
- 逻辑否定
operator!()
(或operator not()
) - 按位倒置
operator~()
(或operator compl()
) - 比较运算符
operator==()
,operator!=()
,operator<()
,operator>()
,operator<=()
和operator>()
- (二进制)算术运算
operator+()
,operator-()
,operator*()
,operator/()
,operator%()
- (二进制)位运算
operator&()
(或operator bitand()
),operator|()
(或operator bit_or()
),operator^()
(或operator xor()
) - 按位移
operator<<()
和operator>>()
- 逻辑
operator||()
(或operator or()
)和operator&&()
(或operator and()
) - 运算
operator@=()
/赋值operator@=()
(for@
是一个合适的运算符符号() - 序列
operator,()
(为此重载实际上杀死了序列属性!) - 指针指向成员的访问
operator->*()
- 内存pipe理
operator new()
,operator new[]()
,operator new[]()
和operator delete[]()
可以作为成员或非成员重载的运算符不像其他运算符那样需要进行基本对象维护。 这并不是说他们不重要。 实际上,这个列表包含一些运算符,它们是否应该是可重载的(例如,
operator&()
的地址或者通常引起sorting的运算符,即operator,()
,operator||()
,和operator&&()
。 - 前后递增/递减
当然,C ++标准并没有给出为什么事情按照他们的方式完成的理由(也没有关于这些决策的早期logging)。 最好的基本原理可以在Bjarne Stroustrup的“Design and Evolution of C ++”中find。 我记得在那里讨论过运营商,但似乎没有电子版。
总的来说,除了潜在的复杂性之外,我认为除了这些限制之外,其他的限制并不是很有价值的。 然而,我怀疑这种限制可能会被解除,因为与现有软件的交互必然以不可预知的方式改变某些程序的含义。
理由是,他们不是非成员是没有意义的,因为操作符左侧的事物必须是一个类实例。
例如,假设一个类A
A a1; .. a1 = 42;
最后一个声明真的是这样的一个电话:
a1.operator=(42);
这对于LHS的东西是没有意义的。 不是A的一个实例,所以函数必须是成员。
因为你不能修改原始types的语义。 定义operator=
如何在int
,如何引用指针或数组访问如何工作是没有意义的。
这里有一个例子:当你重载一个class T
的<< operator
,签名将是:
std::ostream operator<<(std::ostream& os, T& objT )
在哪里实施需要
{ //write objT to the os return os; }
对于<<
运算符,第一个参数需要是ostream对象,第二个参数是类T对象。
如果你试图将operator<<
定义为一个成员函数,你将不能把它定义为std::ostream operator<<(std::ostream& os, T& objT)
。 这是因为二元运算符成员函数只能带一个参数,调用对象作为第一个参数被隐式地传入。
如果使用std::ostream operator<<(std::ostream& os)
签名作为成员函数,实际上最终会得到一个成员函数std::ostream operator<<(this, std::ostream& os)
不做你想做的事。 因此,您需要一个不是成员函数的运算符,并且可以访问成员数据(如果您的类T具有要stream式传输的私有数据, operator<<
需要成为类T的一个好友)。