前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++多态实现原理

C++多态实现原理

作者头像
gaigai
发布2021-03-06 22:28:51
5070
发布2021-03-06 22:28:51
举报
文章被收录于专栏:Windows开发Windows开发

系统学习Windows客户端开发

多态是C++的一个重要特性,本文将深入介绍C++编译器是如何实现C++多态特性。

先看一段涉及多态的代码。

代码语言:javascript
复制
#include <iostream>
using namespace std;

class BaseClass
{
public:
    virtual ~BaseClass() {}
    virtual void VirtualFunction1() { 
        cout << "I am base virtual function 1" << endl; 
    }
    virtual void VirtualFunction2() { 
        cout << "I am base virtual function 2" << endl; 
    }
    void NonVirtualFunction() { 
        cout << "I am base non virtual function" << endl; 
    }
private:
    int m_nBaseField = 0;
};

class SubClass : public BaseClass 
{
public:
    virtual ~SubClass() {}
    virtual void VirtualFunction1() override { 
        cout << "I am subclass function 1, override base class" << endl; 
    }
    void NonVirtualFunction() { 
        cout << "I am subclass non virtual function" << endl; 
    }
private:
    int m_nSubClassField = 0;
};

int main()
{
    BaseClass baseClass;
    SubClass subClass;
    BaseClass* pBaseClass = &baseClass;
    pBaseClass->VirtualFunction1();
    pBaseClass = &subClass;
    pBaseClass->VirtualFunction1();
    pBaseClass->NonVirtualFunction();
    return 0;
}

如上示例代码,定义基类BaseClass,BaseClass定义了虚析构函数、虚函数VirtualFunction1、虚函数VirtualFunction2、非虚函数NonVirtualFunction。

然后定义子类SubClass继承于BaseClass,覆盖重写BaseClass的虚析构函数、虚函数VirtualFunction1、函数NonVirtualFunction,未覆盖重写BaseClass的虚函数VirtualFunction2。

Main函数定义了两个对象baseClass和subClass,定义了BaseClass指针pBaseClass,先让pBaseClass指向baseClass对象,调用VirtualFunction1,然后让pBaseClass指向subClass,调用VirtualFunction1和NonVirtualFunction。

下面是输出结果:

从输出结果知:

第一,pBaseClass指向baseClass对象,调用VirtualFunction1时,是调用BaseClass::VirtualFunction1。当pBaseClass指向subClass对象,调用VirtualFunction1时,是调用SubClass::VirtualFunction1。也就是说,pBaseClass同样调用VirtualFunction1,但是会因为pBaseClass指向的对象不同,而产生不同的行为(调用不同的方法),这就是多态。

第二,pBaseClass指向subClass对象,调用NonVirtualFunction时,调用的是BaseClass::NonVirtualFunction,而不是SubClass::NonVirtualFunction。

接下来将重点解释下这两个结论,首先让我们看下baseClass和subClass这两个对象的内存布局。

从上图可以发现,baseClass有一个数据成员__vfptr,__vfptr是为了实现多态,编译器在最前面隐式添加的一个指针,叫虚函数表指针,其指向虚函数表。baseClass虚函数表有三个元素,对应BaseClass定义的三个虚函数(虚构函数、VirtualFunction1、VirtualFunction2),这三个虚函数的地址是编译阶段编译器计算生成。

subClass继承于baseClass,内存布局上首先是baseClass的__vfptr和数据成员m_nBaseField,然后是SubClass定义的数据成员m_nSubClassField。subClass的__vfptr的值与baseClass的__vfptr的值不同,说明它们指向的虚函数表是不一样的。由于subClass没有新增虚函数,所以虚函数表也是三个元素与BaseClass的虚函数个数相同。但是,subClass覆盖重写了baseClass的虚析构函数和VirtualFunction1,所以第一和第二个元素的值不同,指向的是SubClass对应的虚函数。由于VirtualFunction2未被覆盖重写,所以第三个元素的值与BaseClass的虚函数表的第三个元素的值相同,都指向BaseClass::VirtualFunction2。

NonVirtualFunction不是虚函数,所以没有在虚函数表中。

接下来,我将结合虚函数表和汇编代码对上面的结论进行解释说明。

指针pBaseClass指向subClass对象,为什么调用NonVirtualFunction时不是调用subClass的NonVirtualFunction方法?这是因为pBaseClass是BaseClass*类型,NonVirtualFunction方法不是虚函数,所以它的调用地址在编译阶段,编译器就已经决定调用CBaseClass::NonVirtualFunction方法,我们从如下汇编代码知道call指令的地址固定为0A61519h。

指针pBaseClass指向subClass对象,为什么调用VirtualFunction1方法时,是调用subClass::VirtualFunction1呢?让我们先看下汇编代码。

首先,将指针pBaseClass的值(subClass对象的地址)放入寄存器eax。

其次,读取eax所指向的内存的值放入寄存器edx,也就是pBaseClass->__vfptr的值(虚函数表指针的值)。

再次,读取edx+4所指向的内存的值放入eax,也就是pBaseClass->__vfptr[1],也就是VirtualFunction1函数的地址。

最后,call eax,也就是call VirtualFunction1。

可见,调用虚函数和调用非虚函数有很大区别,调用虚函数时是在运行时期决定的,而调用非虚函数是在编译时期就决定了。调用虚函数时会查询虚函数表对应函数的地址,如果子类覆盖重写基类的虚函数,子类的虚函数表对应函数的地址就会被编译器更新为子类的函数。子类对象的地址可以赋值给基类(BaseClass* pBaseClass = &subClass),由于__vfptr数据成员是从基类继承,所以pBaseClass虽然指向SubClass对象,但是可以访问到__vfptr,而此时__vfptr指向SubClass的虚函数表,自然就调用SubClass的方法。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Windows开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档