虚函数的奇怪行为

时间:2013-01-28 12:08:36

标签: c++ inheritance

使用以下代码,我希望输出为B.f B.f DD.f,但我获得的输出是B.f B.f B.f.如果DD派生自D f为虚拟,那怎么可能呢?

class B
{
public:
    void f() { cout << "B.f "; }
};

class D : public B
{
public:
    virtual void f() { cout << "D.f "; }
};

class DD : public D{
public:
    virtual void f() { cout << "DD.f "; }
};

B * b = new B();
B * d = new D();
B * dd = new DD();

b->f();
d->f();
dd->f();

5 个答案:

答案 0 :(得分:4)

函数从声明virtual的级别变为virtual。您首先在f中声明virtual D,这意味着动态调度只会从D向上发生。 B没有,也不应该知道派生类。

考虑编译器如何看待它:

您有指向B的指针 - B具有以下定义:

class B
{
public:
    void f() { cout << "B.f "; }
};

由于f不是virtual,我会继续静态解决通话 - 即B::f()

答案 1 :(得分:2)

要从指向B的指针进行动态调度,您需要在f()中虚拟B

class B
{
public:
    virtual void f() { cout << "B.f "; }
};

答案 2 :(得分:1)

您需要将B::f()设置为虚拟,而不将B::f()设置为虚拟,它不会出现在B虚拟表(vtbl)中,因此调用B::f()而不是调度调用派生类。

class B
{
public:
   virtual void f() { cout << "B.f "; }
};

答案 3 :(得分:0)

只要在B中声明f()virtual,就会开始维护一个虚拟表,该表包含派生类同名的所有其他函数的函数指针。这是一个查找表,用于以动态/后期绑定方式解析函数调用。

答案 4 :(得分:0)

当你使用引用或指针来调用方法时,编译器会在方法的声明中搜索指针或引用的类型(这里它在B中搜索带有签名的某些方法的声明f())。当它找到一个:

  • 如果它未标记为virtual,则它将其解析为对此类定义的方法的调用 - 这是一个静态绑定。
  • 如果标记为virtual,则调用的方法将是引用或指向的对象中的适当方法 - 这是动态绑定。

接下来的测试应该是:

DD * dd = new DD();
D * d = dd;
B * b = d;

b->f();
d->f();
dd->f();

使用/查看不同的单个对象new DD() ...每种类型都可以被视为您对对象的一种视图。如果您将其视为B,那么f()会执行相同的操作,但如果您将其视为DDDf()会执行某项操作不同...

如果你在街上遇到某人,他向你致敬的标准方式是打个招呼,但对于同一个人,当他遇到他的朋友时,他可以打个招呼!还是哟! :

class Person {
public:
  void salute() { cout << "Hello" << endl; }
};
class Friend : public Person {
public:
  virtual void salute() { cout << "Hi!" << endl; }
};
class RoomMate : public Friend {
public:
  virtual void salute() { cout << "Yo!" << endl; }
};
void asACustomer(Person &p) {
   p.salute(); // static binding, we need the standard politeness
}
void asAFriend(Friend &f) {
    p.salute(); // dynamic binding, we want an appropriate message...
}
RoomMate joe;
asCustomer(joe);
asFriend(joe);

使用静态绑定,您可以在编译时知道调用哪个方法;动态绑定你不能,你只知道一个合适的。这是子类型多态性的关键点。

通常,在混合方法的静态和动态绑定时要小心。