编译器如何知道是使用成员运算符重载还是使用全局运算符重载?

时间:2018-10-18 10:42:21

标签: c++ operator-overloading global member ambiguity

我对c ++运算符有疑问,希望在这里找到答案。 问题的简短版本在标题中,但是如果我真正要问的是任何疑问,这里是长版本。

c ++运算符可以重载,因此可以编写如下内容:

MyClass a(1), b(2);
Myclass c = a + b;

据我了解,典型的实现方式如下:

class MyClass
{
    private:
        int val;
    public:
        explicit MyClass(int _val);

        MyClass operator+(MyClass const& other) const;
        MyClass operator+(int i) const;
};

在这种情况下,它还包含类型为int的重载,这使得可以编写如下内容:

MyClass a(1);
Myclass b = a + 2;

但不是这样:

MyClass a(1);
Myclass b = 2 + a;

因为这就像调用2.operator+(a),而2不是对象。由于程序员希望以一种可能的方式重载运算符,因此有另一种实现它们的方式,如下所示:

class MyClass
{
    private:
        int val;
    public:
        explicit MyClass(int _val);

        friend MyClass operator+(MyClass const& lhs, MyClass const& rhs);
        friend MyClass operator+(int lhs, MyClass const& rhs);
        friend MyClass operator+(MyClass const& lhs, int rhs);
};

允许所有三种类型的添加。

现在,令我困扰的是:如果我们同时实施这两者又该怎么办?编译器如何决定使用成员运算符还是全局运算符?

说句公道话,在任何明智的实现中,调用哪个运算符都无关紧要,它们既不应返回不同的东西,也不应该具有不同的副作用,但是我尝试实现它以查看会发生什么:

class MyClass
{
    private:
        int val;
    public:
        explicit MyClass(int _val) : val(_val){}

        MyClass operator+(MyClass const& other) const
        {
            cout << "Call to member operator+ for MyClass+MyClass" << endl;
            return MyClass(val + other.val);
        }

        MyClass operator+(int other) const
        {
            cout << "Call to member operator+ for MyClass+int" << endl;
            return MyClass(val + other);
        }

        friend MyClass operator+(MyClass const& lhs, MyClass const& rhs) 
        {
            cout << "Call to global operator+ for MyClass+MyClass " << endl;
            return MyClass(lhs.val + rhs.val);
        }

        friend MyClass operator+(int lhs, MyClass const& rhs) 
        {
            cout << "Call to global operator+ for int+MyClass " << endl;
            return MyClass(lhs + rhs.val);
        }

        friend MyClass operator+(MyClass const& lhs, int rhs) 
        {
            cout << "Call to global operator+ for MyClass+int " << endl;
            return MyClass(lhs.val + rhs);
        }
};


int main() {
    MyClass a(1), b(2);
    int i(3);
    MyClass r_0 = a.operator+(b);
    MyClass r_1 = a.operator+(i);
    MyClass r_2 = operator+(a,b);
    MyClass r_3 = operator+(a,i);
    MyClass r_4 = operator+(i,a);
    MyClass r_5 = a + b;
    MyClass r_6 = a + i;
    MyClass r_7 = i + a;

    return 0;
}

编译并打印

Call to member operator+ for MyClass+MyClass
Call to member operator+ for MyClass+int
Call to global operator+ for MyClass+MyClass 
Call to global operator+ for MyClass+int 
Call to global operator+ for int+MyClass 
Call to global operator+ for MyClass+MyClass 
Call to global operator+ for MyClass+int 
Call to global operator+ for int+MyClass

我很容易以为这一切都是合法的,并且全球运营商优先于会员,但是我唯一能在网上找到的东西似乎暗示这些增加是模棱两可的,所以真的是这样吗?还是我只是在这里查看未定义的行为?

1 个答案:

答案 0 :(得分:2)

成员函数和非成员函数参与平等权利的重载决议。为了使它们具有可比性,编译器使用隐式对象参数扩展了每个成员函数。 ([over.match.funcs]/p2):

  

候选函数集可以包含要针对同一参数列表解析的成员和非成员函数。为了使参数列表和参数列表在此异构集合中具有可比性,因此成员函数被视为具有额外的第一个参数,称为隐式对象参数,该参数代表已为其调用成员函数的对象。出于重载解析的目的,静态和非静态成员函数均具有隐式对象参数,而构造函数则没有。

[over.match.funcs]/p5

  

在重载解析过程中,隐含对象参数与​​其他参数没有区别。但是,隐式对象参数保留其标识,因为无法应用用户定义的转换来实现与之的类型匹配。

鉴于隐式对象参数还继承了非静态成员函数的ref-和cv-qualification,这基本上意味着从编译器的角度来看,成员运算符声明为:

MyClass MyClass::operator+(int) const;

在某种程度上等效于:

MyClass operator+(const MyClass&, int);

唯一区别于常规非成员函数的例外是,第一个参数(隐式对象)不考虑用户定义的转换(这就是1 + a将切勿使用某些转换构造函数1A转换为A(int)来调用A::operator+(const A&)),并且临时实例可以绑定为非常量合格成员函数生成的非常量引用。

成员和全局operator+在您的代码中含糊不清,因此应产生错误。