多态性和成员函数指针是如何工作的

How does polymorphism and member function pointers works?

本文关键字:工作 何工作 成员 函数 指针 多态性      更新时间:2023-10-16

我有以下代码:

#include <iostream>
using namespace std;
class Base 
{
public:
    virtual void WhoAmI() const;
    typedef void (Base::*WhoPtr)() const;
};
class Derived : public Base 
{
public:
    virtual void WhoAmI() const;
};
void Derived::WhoAmI() const 
{
    cout << "I am the derived" << endl;
}
void Base::WhoAmI() const 
{
    cout << "I am the base" << endl;
}
int main() 
{
    Base::WhoPtr func = &Base::WhoAmI;
    Base theBase;
    (theBase.*func)();
    Derived theDerived;
    (theDerived.*func)();
    cin.get();
    return 0;
}   

让我们关注主要内容:

int main() 
{
    Base::WhoPtr func = &Base::WhoAmI;
    Base theBase;
    (theBase.*func)();
    Derived theDerived;
    (theDerived.*func)();
    cin.get();
    return 0;
}   

我们有一个局部变量func,它持有Base::WhoAmI的地址。

此外,我们还有BaseDerived对象。

在第2行中,我们从底部调用有点的func(theBase.*func)()

直到现在我才明白。

2行之后,我们从派生的中调用此:(theDerived.*func)()

它打印:I am the derived。为什么?

两个WhoAmI都是virtual,这意味着调用依赖于pointed object,而不是类型。

所指向的对象是属于CCD_ 13的CCD_。为什么打印I am the derived而不是I am the base

你为什么感到惊讶。您有一个指向成员函数的指针指向虚拟函数。如果你取了theDerived或对其的引用,并初始化Base*或有了Base&,您会期望ptrToBase->WhoAmI()调用派生类中的函数。毕竟,这就是你使用一开始是一个虚拟函数。当你通过指向成员函数的指针调用。表达式CCD_ 20产生一个指向(虚拟)成员函数的指针。

指向的对象是theDerived。您选择的方法是Base::whoAmI,请注意,方法名称包含类引用(静态),但不包含对象引用(动态)。调用什么虚拟函数取决于用作该方法的this的对象的运行类型时间。

虚拟函数的全部意义在于,它是运行时根据所讨论对象的动态类型来决定调用哪个版本。这与非虚拟函数调用非常不同,在非虚拟函数中,编译器本身根据对象的声明的类型做出决定,而不管实际的运行时对象是什么类型。

为了实现这一点,每个类都有一个虚拟函数表(vtable),它的所有实例都在运行时有一个隐式指针指向该表。现在,当您创建Base的实例时,该实例的vtable指针将指向Base的vtable。同样,Derived的实例将有一个指向Derived vtable的指针。

在这两个vtable中,在您的示例中,WhoAmI()只有一个条目,Base的vtable中的指针指向Base::WhoAmI(),Derived的vtable指向Derived::WhoAmI()

因此,当您调用WhoAmI()时,运行时将从对象中查找vtable,然后查找指向将要执行的函数的函数指针。

也就是说,从您所看到的行为中可以明显看出,成员函数指针是什么:只不过是vtable的偏移量!在您的情况下,这个偏移很可能只是零,因为WhoAmI()是vtable中的第一个也是唯一一个条目。当您调用成员函数指针后面的函数时,您会给它一个对象,从中可以查找vtable。然后,vtable(成员函数指针)中的偏移量用于加载指向实际执行代码的指针,就像在对虚拟函数的任何其他调用中一样。

唯一的区别是,在正常的虚拟函数调用中,编译器将通过调用的函数的名称来知道查找函数指针的精确偏移量,当您使用成员函数指针时,该偏移量在运行时由成员函数指针提供。