简体   繁体   English

虚函数行为异常

[英]Strange behavior with virtual functions

With the following code, I would expect output to be Bf Bf DD.f, but instead the output I get is Bf Bf Bf How is that possible, when DD derives from D which has f as virtual. 使用以下代码,我希望输出为Bf Bf DD.f,但是我得到的输出为Bf Bf Bf当DD从具有f为虚数的D派生时,这怎么可能。

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();

Functions become virtual from the level they were declared virtual up. 函数从被声明为virtual的级别开始变为virtual You first declare f virtual in D , which means the dynamic dispatch will only happen from D upwards. 您首先在D声明f virtual ,这意味着动态分配将仅从D向上进行。 B doesn't, nor should it know about the deriving classes. B没有,也不应该知道派生类。

Think about how the compiler sees it: 考虑一下编译器如何看待它:

You have a pointer to B - B has the following definition: 您有一个指向B的指针B具有以下定义:

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

Since f is not virtual , I'll just go ahead and resolve the call statically - ie B::f() . 由于f不是virtual ,所以我将继续静态解决该调用-即B::f()

For dynamic dispatch to work from a pointer to B , you need to make f() virtual in B : 为了使动态分配从指向B的指针开始工作,您需要在B中将f()虚拟化:

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

You need to set B::f() to virtual, without set B::f() to virtual, it won't appear in B virtual table(vtbl), thus B::f() is called instead of dispatch the call to derived class. 您需要将B::f()设置为虚拟,而不将B::f()为虚拟,它不会出现在B虚拟表(vtbl)中,因此调用B::f()而不是调度调用派生类。

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

As soon as you declare f() virtual in B,this starts to maintain a virtual table that holds the function pointers of all the other functions of same name of derived classes. 在B中将f()声明为虚拟后,这便开始维护一个虚拟表,该表包含具有相同名称的派生类的所有其他函数的函数指针。 This is a lookup table that is used to resolve function calls in a dynamic/late binding manner. 这是一个查找表,用于以动态/后期绑定方式解析函数调用。

When you use a reference or a pointer to call a method, the compiler searches in the type of the pointer or reference at the declaration of the method (here it searches in B the declaration of some method with signature f() ). 当您使用引用或指针来调用方法时,编译器会在该方法的声明中搜索指针或引用的类型(此处,它在B搜索带有签名f()的某个方法的声明)。 When it finds one : 当找到一个:

  • if it is NOT marked as virtual , then it solves it as a call to the method defined for this class - this is a static binding. 如果未将其标记为virtual ,则将其解决为对该类定义的方法的调用-这是静态绑定。
  • if it is marked as virtual , then the method called will be the appropriate one of the object referenced or pointed by - this is a dynamic binding. 如果将其标记为virtual ,则调用的方法将是所引用或指向的对象中的合适对象之一-这是动态绑定。

The next test would have been : 下一个测试应该是:

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

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

One single object new DD() that is used/viewed differently... Each type can be thought as a kind of view you have on an object. 一个单独的对象new DD()的使用/查看方式有所不同...每种类型都可以认为是您对对象的一种查看。 If you see it as a B then f() does something but always the same thing, but if you see it as D or DD , f() does something different... 如果将其视为Bf()执行某些操作,但始终是相同的,但是如果将其视为DDD ,则f()执行不同的操作...

If you meet someone in the street, the standard way for him to salute you is to say hello, but for the same person, when he meets a friend of him he can either say hi! 如果您在街上遇到某人,向他致敬的标准方法是打个招呼,但对于同一个人,当他遇到他的一个朋友时,他要么打个招呼! or Yo! 或哟! :

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);

With static binding you know at compile time which method is called; 使用静态绑定,您可以在编译时知道调用哪个方法。 with dynamic binding you cannot, you only know that an appropriate one will be. 使用动态绑定,您将无法做到,只知道合适的绑定就可以了。 This is a key point in sub-typing polymorphism. 这是子类型多态性的关键点。

In general, be careful when mixing static and dynamic binding for a method. 通常,将静态绑定和动态绑定混合用于方法时要小心。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM