【问题标题】:Polymorphism with references does not behave as expected具有引用的多态性不符合预期
【发布时间】:2015-03-01 19:30:57
【问题描述】:

据我了解,引用的多态性应该与指针的工作方式完全相同。

但是,请考虑以下示例:使用指针时正确分派了对 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??
}

【问题讨论】:

  • d = c 不会重新分配引用。它在A 上调用复制赋值运算符。

标签: c++ inheritance polymorphism


【解决方案1】:

在这里你指定一个参考:

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 !

【讨论】:

  • 引用不是指针。它可以这样实现,但这是定义的实现。
  • @black 我完全同意。我的意思是它 like 是一个指针(因为这是 OP 似乎掌握得很好的东西)。我已经编辑以避免任何歧义!
  • 我建议添加此 clarification 以消除任何进一步的疑问,恕我直言。
  • 虽然答案就在这里,但恕我直言,它被一些不必要的细节所稀释。 @0x499602D2 得到了关键细节,分配给引用只需调用 lhs 上的赋值运算符。我知道引用是什么,我知道赋值运算符是什么以及它的行为方式,我只是没有意识到发生了什么。
【解决方案2】:

引用在其整个生命周期内都受其所指对象的约束,并且与指针不同,它需要初始化。使用赋值运算符调用引用的赋值运算符,并且不重新分配引用:

A &d = b;
d = c;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-02
    • 1970-01-01
    • 2013-01-29
    • 1970-01-01
    • 1970-01-01
    • 2017-06-04
    • 2016-03-27
    相关资源
    最近更新 更多