为什么C++赋值运算符重载函数不能被继承

标题的问题同样可以是:为什么赋值运算符operator=()不能使用虚函数。

有一篇文章说得比较好,连接在此:为什么C++赋值运算符重载函数不能被继承?,推测过程如下:

class A1
{
public:
        int operator=(int a)
        {
                return 8;
        }

        int operator+(int a)
        {
                return 9;
        }
};

class B1 : public A1
{
public:
        int operator-(int a)
        {
                return 7;
        }
};

int main()
{        
        B1 v;
        cout << (v + 2) << endl; // OK, print 9
        cout << (v - 2) << endl; // OK, print 7
        cout << (v = 2) << endl; // Error, see below

        return 0;
}

VC编译器的错误提示:

error C2679: binary '=' : no operator defined which takes a right-hand operand of type 'const int' (or there is no acceptable conversion)

明显是找不到定义,但很明显这份代码的意图就是让派生类对象使用基类继承下来的赋值运算符来返回一个整数。但是却找不到形如const int operator=(const int )的函数定义。

这份代码比较有帮助就是在于,一般的=都是把自身的类型转化为自身的引用。但是这个=确实以int为参数,类类型的引用为返回的操作符。因此能看出问题,所以罗列线索(线索转自链接文章原文):

1,每一个类对象实例在创建的时候,如果用户没有定义“赋值运算符重载函数”,那么,编译器会自动生成一个隐含和默认的“赋值运算符重载函数”。所以,B1的实际上的声明应该类似于下面这种情况:
class A1
{
public:
        int operator=(int a)
        {
                return 8;
        }


        int operator+(int a)
        {
                return 9;
        }
};
class B1 : public A1
{
public:
        B1& operator =(const B1& robj); // 注意这一行是编译器添加的
        int operator-(int a)
        {
                return 7;
        }
};

2,C++标准规定:如果派生类中声明的成员与基类的成员同名,那么,基类的成员会被覆盖,哪怕基类的成员与派生类的成员的数据类型和参数个数都完全不同。显然,B1中的赋值运算符函数名operator =和基类A1中的operator =同名,所以,A1中的赋值运算符函数int operator=(int a);被B1中的隐含的赋值运算符函数B1& operator =(const B1& robj);所覆盖。 A1中的int operator=(int a);函数无法被B1对象访问。

3,程序中语句v = 2实际上相当于v.operator =(2);,但是A1中的int operator=(int a);已经被覆盖,无法访问。而B1中默认的B1& operator =(const B1& robj);函数又与参数2的整数类型不相符,无法调用。

4,为了确认B1中默认的B1& operator =(const B1& robj);函数的存在性,可以用以下代码验证:
B1 b;
B1 v;
v = b; // OK, 相当于调用v.operator =(b);

5,所以,“赋值运算符重载函数”不是不能被派生类继承,而是被派生类的默认“赋值运算符重载函数”给覆盖了。

这就是C++赋值运算符重载函数不能被派生类继承的真实原因!

这里面呢,我发现了一点我觉得有问题的地方,就在于第2条“如果派生类中声明的成员与基类的成员同名,那么,基类的成员会被覆盖”,只有虚函数才有这种覆盖的特性,如果是非虚函数的话,还是以对象的引用或者指针的类型决定了operator=()使用基类还是派生类的。因此,在文章的例子中出现的问题并不在于A1的方法是否被覆盖,而是由于“B1中默认的B1& operator =(const B1& robj);函数又与参数2的整数类型不相符,无法调用”。这就回答了原标题的问题:“为什么赋值运算符不能被继承”,如果本例的计算流程变一下,类型匹配之后也许会发现是可以被继承的。

        这之后,再回到自己提的问题“为什么赋值运算符operator=()不能使用虚函数”。

假设基类中的operator=()为虚函数,同样举个例子:

DerivedClass a;//派生类某对象a

DerivedClass b;//派生类某对象b

BaseClass &c = a;//基类某指针、或者引用都行c

BaseClass &d = b;//基类某指针、或者引用都行d

a = b;//调用A.operator=(const DerivedClass & B)

如果B中忘记自己定义的赋值操作符了,那么编译器会给我们生成默认的操作符,在a的拷贝过程中,将会采用此默认的operator=(),即浅拷贝赋值的方法逐成员调用=(注:派生类成员),这对非动态管理的派生类没有问题,但是对于有字符串或new操作的派生类来说却是不行的。但是呢,如果由用户自己重新定义了虚赋值操作符,那么通过正确的改写是可以正确地实现赋值。

反观如果不用虚函数,使用基类的赋值操作符来进行逐成员赋值(注:基类成员改变、派生类成员不变)同样会产生问题。所以结论和之前的一样,不是不行,而是需要结合具体的情况去正确地实现。


猜你喜欢

转载自blog.csdn.net/m0_38084180/article/details/80553804