虚拟表

虚拟表

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

因为了解虚拟表的工作原理并不需要使用虚拟功能,所以这部分可以被认为是可选的阅读。

虚拟表格其实很简单,尽管用文字描述有点复杂。首先,每个使用虚函数的类(或者从使用虚函数的类派生)都被赋予它自己的虚表。这个表只是编译器在编译时设置的一个静态数组。一个虚拟表包含一个可以被类的对象调用的虚拟函数的条目。这个表中的每个条目都只是一个函数指针,指向那个类可以访问的派生最多的函数。

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

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

 

 

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

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

 

 

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

现在,我们来讨论一下这些虚拟表格是如何填充的。因为这里只有两个虚拟函数,每个虚拟表将有两个入口(一个用于函数1(),另一个用于函数2())。请记住,填写这些虚拟表时,每个条目都填写了该类类型的对象可以调用的派生最多的函数。

基础对象的虚拟表格很简单。Base类型的对象只能访问Base的成员。基地不能访问D1或D2功能。因此,函数1的条目指向Base :: function1(),函数2的条目指向Base :: function2()。

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

D2的虚拟表与D1类似,除了函数1的条目指向Base :: function1(),并且函数2的条目指向D2 :: function2()。

这是一张图片:

虽然这张图看起来很疯狂,但实际上很简单:每个类中的* __ vptr都指向该类的虚拟表。虚拟表中的条目指向允许调用该类的函数对象的派生最多的版本。

所以考虑一下当我们创建一个D1类型的对象时会发生什么:

 

 

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

现在,我们设置一个基地指针到D1:

 

 

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

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

 

 

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

现在,你可能会说:“但是如果Base真的指向一个Base对象而不是一个D1对象。它仍然会打电话给D1 :: function1()?“。答案是不。

 

 

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

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

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

另外值得一提的是,任何使用虚拟函数的类都有一个__vptr,因此该类的每​​个对象都会被一个指针放大。虚拟功能是强大的,但他们确实有一个性能成本。

posted @ 2018-01-29 17:02 史D芬周 阅读( ...) 评论( ...) 编辑 收藏
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Qlik 中,创建虚拟可以使用“连接”操作来实现。以下是一个简单的示例,说明如何创建一个包含所有月份的虚拟: 1. 在 Qlik 中,打开脚本编辑器。 2. 创建一个包含所有月份的格。可以使用以下脚本: ``` TempCalendar: LOAD Date(monthstart(Date#('01/'&IterNo(),'MM/DD/YYYY'))) as Date, month(Date(monthstart(Date#('01/'&IterNo(),'MM/DD/YYYY')))) as Month, year(Date(monthstart(Date#('01/'&IterNo(),'MM/DD/YYYY')))) as Year, 'Q' & Ceil(month(Date(monthstart(Date#('01/'&IterNo(),'MM/DD/YYYY'))))) / 3 as Quarter, Week(Date#('01/'&IterNo()&'/2000','MM/DD/YYYY')) as Week, Date(monthstart(Date#('01/'&IterNo()&'/2000','MM/DD/YYYY')),'YYYYMM') as YearMonth, AutoNumber(Date(monthstart(Date#('01/'&IterNo()&'/2000','MM/DD/YYYY'))),'YYYYMM') as YearMonthID While IterNo()<=12 ``` 该脚本将创建一个包含月份、季度、年份、周等信息的格。 3. 将该格加载到 Qlik 中: ``` Calendar: LOAD Date, Month, Year, Quarter, Week, YearMonth, YearMonthID Resident TempCalendar Order By Date ASC; ``` 该脚本将 TempCalendar 中的数据加载到名为 Calendar 的中,并按日期升序排序。 4. 将 Calendar 与实际数据进行关联。可以使用以下脚本: ``` Data: LOAD Date, Value FROM [DataFile.xlsx] (ooxml, embedded labels, table is Sheet1); Left Join (Data) LOAD Date, Month, Year, Quarter, Week, YearMonth, YearMonthID Resident Calendar; ``` 该脚本将从名为 DataFile.xlsx 的文件中加载实际数据,并将其与 Calendar 进行左连接。这将确保所有月份都包含在结果中。 希望这个示例能够帮助你创建虚拟

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值