带有引用的多态性没有按预期运行

Polymorphism with references does not behave as expected

本文关键字:运行 多态性 引用      更新时间:2023-10-16

据我所知,引用的多态性应该和指针的多态性一样工作。

然而,请考虑以下示例:当使用指针时,对doer()的调用是正确调度的,但当使用引用时,似乎在这两种情况下都调用了"B版本"。

我不明白下面的例子为什么会这样。为什么在使用引用时,在这两种情况下都调用"B版本"

#include <iostream>
class B;
class C;
void doer(B *x) {
    std::cout << "B version" << std::endl;
}
void doer(C *x) {
    std::cout << "C version" << std::endl;
}
class A {
public:
    virtual ~A() {}
    virtual void doit() = 0;
};
class B: public A {
public:
    virtual void doit() override {
        doer(this);
    }
};
class C: public A {
public:
    virtual void doit() override {
        doer(this);
    }
};
int main() {
    B b;
    C c;
    A *a = &b;
    a->doit(); // B version gets called, OK
    a = &c;
    a->doit(); // C version is called, OK
    A &d = b;
    d.doit(); // B version is called, OK
    d = c;
    d.doit(); // B version is called again??
}

在这里分配一个引用:

A &d = b;
d.doit(); // B version is called, OK

在这里,用c覆盖d引用的对象(它不再是引用的定义):

d = c;
d.doit(); // B version is called again??

这就是引用和指针之间的主要区别。引用就像一个常量指针,只能在定义时指定。之后,每当你使用引用时,它就意味着你引用的对象

事实上,当您执行d = c;时,会发生一些切片。对象d实际上是一个B,但来自a的operator=被调用,只复制a的成员数据。

以下是如何证明这一声明:

class A {
public:
    ...
    A& operator= (A a) {
        cout << "A::operator=" << endl;  // just to show what happens when d=c is called
        return *this; 
    }
};
class B : public A {
public:
    int x;   // add some variables for B
    virtual void doit() override {
        cout << "B::doit() " << x << endl; 
        doer(this);
    }
};
class C : public A {
public:
    int a,b; // add aditional variables for C
    virtual void doit() override {
        cout << "C::doit() " << a << ","<<b << endl;
        doer(this);
     }
};
...
b.x = 123;  // put some class specific variables in C
c.a = 222;  c.b = 333;  // put some class specific variables in C
A &d = b;  // assignement of the reference.  d reffers to a b object
d.doit();  // B version is called, OK
d = c;     // but an A object is copied (so only A subobject of c is taken
           // to overwrite A subobject of d)
d.doit();  // B version is called becaus again?? => yes !! because it's still a B
           // And you see that the B part of the object is left intact by A::operator=
cout << typeid(d).name() << endl; 
           // remember at this point that d still refers to b !

引用在其整个生命周期中都由其引用对象绑定,并且与指针不同,需要初始化。使用赋值运算符调用引用的赋值运算符,是否重新分配引用:

A &d = b;
d = c;

这里,d = cd中包含的基类A子对象调用赋值运算符,并复制c中包含的A子对象数据。