使用引用的多态行为与预期不符。

3

据我了解,使用引用的多态应该与使用指针的多态完全相同。

但请考虑以下示例:当使用指针时,对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??
}

7
d = c 不会重新分配引用。它在 A 上调用了复制赋值运算符。 - David G
2个回答

3

在这里,您可以分配一个引用:

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 !

2
一个引用不是指针。它可以被实现为指针,但这取决于具体的实现。 - edmz
@black 我完全同意。我的意思是它类似于指针(因为这是OP似乎掌握得很好的东西)。我已经编辑过了,避免了任何歧义! - Christophe
我建议添加这个澄清,以消除任何进一步的疑虑,仅供参考。 - edmz
1
虽然答案在这里,但我认为它被一些不必要的细节所淡化。@0x499602D2得到了关键细节,即将引用分配给lhs只是调用赋值运算符。我知道什么是引用,我知道什么是赋值运算符以及它的行为,我只是没有意识到正在发生什么。 - Stefano Sanfilippo

1

引用在其生命周期内始终绑定于它们的引用对象,并且与指针不同,需要初始化。使用赋值运算符调用引用对象的赋值运算符,而不是重新分配引用:

A &d = b;
d = c;

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接