为什么某些运算符只能作为成员函数重载,其他作为友元函数,其余的作为两者?

时间:2009-07-15 16:55:16

标签: c++ class operator-overloading member-functions

为什么某些运算符只能作为成员函数重载,其他作为非成员“自由”函数,其余的作为两者?

这些背后的理由是什么?

如何记住哪些运营商可以超载(成员,免费或两者)?

4 个答案:

答案 0 :(得分:30)

该问题列出了三类运营商。我认为,将它们放在一个列表中有助于理解为什么一些运营商在可以超载的地方受到限制:

  1. 必须作为成员重载的运营商。这些是相当少的:

    1. 作业operator=()。允许非成员分配似乎为操作员劫持任务打开了大门,例如,通过重载不同版本的const资格。鉴于赋值运算符相当基础,似乎是不可取的。
    2. 函数调用operator()()。函数调用和重载规则足够复杂。通过允许非成员函数调用操作符来进一步使规则复杂化似乎是不明智的。
    3. 下标operator[]()。使用有趣的索引类型似乎可能会干扰对运营商的访问。虽然劫持超载几乎没有什么危险,但似乎并没有多大的好处,但写出高度非显而易见的代码的潜力很大。
    4. 班级成员访问operator->()。副手我无法看到任何非运营商超载此运营商的行为。另一方面,我也看不到任何。此外,类成员访问操作符具有相当特殊的规则,并且潜在的重载干扰这些似乎是不必要的复杂化。
    5. 虽然可以想象重载这些成员中的每一个都是非成员(特别是下标操作符,它可以在数组/指针上运行,并且这些可以在调用的任一侧)但是,如果例如一个赋值,它似乎很令人惊讶可能被非成员重载劫持,这是一个比成员分配更好的匹配。这些运算符也非常不对称:您通常不希望支持涉及这些运算符的表达式两边的转换。

      那就是说,例如,对于一个lambda表达式库,如果可以使所有这些运算符超载,那就太好了,我不认为有一个固有的技术原因可以阻止这些运算符过载。 / p>

    6. 必须作为非成员函数重载的运算符。

      1. 用户定义的文字operator"" name()
      2. 这个算子有点奇怪,可能并不是真正的算子。在任何情况下,没有对象可以调用此成员来定义成员:用户定义的文字的左参数始终是内置类型。

      3. 问题中没有提到,但也有运营商根本无法超载:

        1. 成员选择器.
        2. 指向成员的对象访问运算符.*
        3. 范围运算符::
        4. 三元运算符?:
        5. 这四个运营商被认为太根本不能干涉。虽然有人建议允许在某些时候重载operator.(),但是没有强有力的支持(主要用例是智能引用)。虽然确实存在一些可以想象的环境,但也可以使这些运算符超载。

        6. 可以作为成员或非成员重载的运营商。这是大部分运营商:

          1. 前后增量/减少operator++()operator--()operator++(int)operator--(int)
          2. [一元]解除引用operator*()
          3. operator&()
          4. 的[一元]地址
          5. [一元]签署operator+()operator-()
          6. 逻辑否定operator!()(或operator not()
          7. 按位反转operator~()(或operator compl()
          8. 比较operator==()operator!=()operator<()operator>()operator<=()operator>()
          9. [二元]算术operator+()operator-()operator*()operator/()operator%()
          10. [二进制]按位operator&()(或operator bitand()),operator|()(或operator bit_or()),operator^()(或operator xor())< / LI>
          11. 按位移位operator<<()operator>>()
          12. 逻辑operator||()(或operator or())和operator&&()(或operator and()
          13. 操作/作业operator@=()@是合适的操作符号()
          14. 序列operator,()(重载实际上会杀死序列属性!)
          15. 指针指向成员访问operator->*()
          16. 内存管理operator new()operator new[]()operator new[]()operator delete[]()
          17. 作为成员或非成员可能过载的运营商不像其他运营商那样进行基本对象维护。这并不是说它们并不重要。实际上,这个列表包含一些运算符,它们是否应该是可重载的(例如,operator&()的地址或通常导致排序的运算符,即operator,(),{{1 }}和operator||()

            当然,C ++标准并没有给出为什么事情按照完成方式完成的理由(并且也没有关于这些决定的早期记录)。最好的理由可以在&#34; C ++的设计和演变&#34;作者:Bjarne Stroustrup。我记得在那里讨论过运营商,但似乎没有电子版本。

            总的来说,我认为除了潜在的并发症之外,除了潜在的并发症之外,还有其他限制因素的确有很强的理由。但是,我怀疑这些限制可能会被取消,因为与现有软件的交互必然会以不可预测的方式改变某些程序的含义。

答案 1 :(得分:8)

基本原理是它们不是非成员是没有意义的,因为运算符左侧的东西必须是类实例。

例如,假设一个A类

A a1;
..
a1 = 42;

最后一句话实际上是这样的一个电话:

a1.operator=(42);

对于的LHS上的内容没有意义。不是A的实例,因此该函数必须是成员。

答案 2 :(得分:6)

因为您无法修改基本类型的语义。定义operator=如何在int上工作,如何使用指针或数组访问如何工作是没有意义的。

答案 3 :(得分:1)

这是一个例子: 当您为<< operator class T重载时,签名将为:

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)。 这是因为二元运算符成员函数只能接受一个参数,并且使用this将调用对象作为第一个参数隐式传入。

如果您使用std::ostream operator<<(std::ostream& os)签名作为成员函数,您实际上最终会得到一个成员函数std::ostream operator<<(this, std::ostream& os),它将无法执行您想要的操作。 因此,您需要一个不是成员函数的运算符,并且可以访问成员数据(如果您的类T具有要传输的私有数据,operator<<需要成为T类的朋友)。