C++基础教程面向对象(学习笔记(58))

虚拟表

为了实现虚函数,C ++使用一种称为虚拟表的特殊形式的后期绑定。该虚拟表是用于解决在动态/后期绑定方式的函数调用函数的查找表。虚拟表有时会使用其他名称,例如“vtable”,“虚函数表”,“虚方法表”或“调度表”。

因为了解虚拟表的工作方式不是使用虚函数所必需的,所以可以将此部分视为可选读取。

虚拟表实际上非常简单,但用文字描述有点复杂。首先,每个使用虚函数的类(或者从使用虚函数的类派生)都有自己的虚拟表。该表只是编译器在编译时设置的静态数组。虚拟表包含可由类的对象调用的每个虚函数的一个条目。此表中的每个条目只是一个函数指针,指向该类可访问的最派生函数。

其次,编译器还会添加一个指向基类的隐藏指针,我们将其称为* __ vptr* __ vptr在创建类实例时自动设置,以便指向该类的虚拟表。与* this指针不同,* this指针实际上是编译器用来解析自引用的函数参数,* __ vptr是一个真正的指针。因此,它使每个类对象的分配大一个指针的大小。它还意味着* __ vptr由派生类继承,这很重要。

到目前为止,你可能对这些东西如何组合在一起感到困惑,所以让我们来看一个简单的例子:

class Base
{
public:
    virtual void function1() {};
    virtual void function2() {};
};
 
class D1: public Base
{
public:
    virtual void function1() {};
};
 
class D2: public Base
{
public:
    virtual void function2() {};
};

因为这里有3个类,编译器将设置3个虚拟表:一个用于Base,一个用于D1,一个用于D2。

编译器还会添加一个隐藏指针,指向使用虚函数的最基类。虽然编译器会自动执行此操作,但我们会将其放在下一个示例中,以显示它的添加位置:

class Base
{
public:
    FunctionPointer *_vptr;
    virtual void function1() {};
    virtual void function2() {};
};
 
class D1: public Base
{
public:
    virtual void function1() {};
};
 
class D2: public Base
{
public:
    virtual void function2() {};
};

创建类对象时,* __ vptr设置为指向该类的虚拟表。例如,当创建Base类型的对象时,* __ vptr设置为指向Base的虚拟表。构造D1或D2类型的对象时,* __ vptr设置为分别指向D1或D2的虚拟表。

现在,我们来谈谈如何填写这些虚拟表。因为这里只有两个虚函数,每个虚拟表将有两个条目(一个用于function1(),一个用于function2())。请记住,当填写这些虚拟表时,每个条目都填充了该类类型的对象可以调用的派生最多的函数。

Base对象的虚拟表很简单。Base类型的对象只能访问Base的成员。Base无法访问D1或D2函数。因此,function1的条目指向Base :: function1(),而function2的条目指向Base :: function2()。

D1的虚拟表稍微复杂一些。D1类型的对象可以访问D1和Base的成员。但是,D1重写了function1(),使得D1 :: function1()比Base :: function1()更加派生。因此,function1的条目指向D1 :: function1()。D1没有覆盖function2(),因此function2的条目将指向Base :: function2()。

D2的虚拟表类似于D1,除了function1的条目指向Base :: function1(),而function2的条目指向D2 :: function2()。

这是图形的图片:

虽然这个图有点疯狂,但它非常简单:每个类中的* __ vptr指向该类的虚拟表。虚拟表中的条目指向允许调用该类的函数对象的最派生版本。

因此,请考虑在创建D1类型的对象时会发生什么:

int main()
{
    D1 d1;
}

因为d1是D1对象,所以d1将其* __ vptr设置为D1虚拟表。

现在,让我们设置一个指向D1的基指针:

int main()
{
    D1 d1;
    Base *dPtr = &d1;
}

请注意,因为dPtr是基指针,所以它只指向d1的Base部分。但是,还要注意* __ vptr位于类的Base部分,因此dPtr可以访问此指针。最后,请注意dPtr - > __ vptr指向D1虚拟表!因此,即使dPtr属于Base类型,它仍然可以访问D1的虚拟表(通过__vptr)。

那么当我们尝试调用dPtr-> function1()时会发生什么?

int main()
{
    D1 d1;
    Base *dPtr = &d1;
    dPtr->function1();
}

首先,程序识别出function1()是一个虚函数。其次,程序使用dPtr - > __ vptr来获取D1的虚拟表。第三,它查找在D1的虚拟表中调用哪个版本的function1()。这已设置为D1 :: function1()。因此,dPtr-> function1()解析为D1 :: function1()!

现在,您可能会说,“但是,如果dPtr真正指向Base对象而不是D1对象,那该怎么办呢?它还会调用D1 :: function1()吗?“ 答案是不。

int main()
{
    Base b;
    Base *bPtr = &b;
    bPtr->function1();
}

在这种情况下,当创建b时,__ vptr指向Base的虚拟表,而不是D1的虚拟表。因此,bPtr - > __ vptr也将指向Base的虚拟表。function1()的Base的虚拟表条目指向Base :: function1()。因此,bPtr-> function1()解析为Base :: function1(),这是Base对象应该能够调用的function1()的派生最多的版本。

通过使用这些表,编译器和程序能够确保函数调用解析为适当的虚函数,即使您只使用指针或对基类的引用!

调用虚函数比调用非虚函数要慢几个原因:首先,我们必须使用* __ vptr来获取相应的虚拟表。其次,我们必须索引虚拟表以找到要调用的正确函数。只有这样我们才能调用该函数。因此,我们必须执行3个操作来查找要调用的函数,而不是正常间接函数调用的2个操作,或者直接函数调用的一个操作。然而,对于现代计算机,这个增加的时间通常是相当微不足道的。

另外作为提醒,任何使用虚函数的类都有*__vptr,因此该类的每​​个对象都会被一个指针变大。虚函数很强大,但它们确实具有性能成本。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值