C++中的多个虚拟表格和虚拟观众
Multiple Vtables ad VPointers in C++
我一直在阅读 vtables 和指针,但我仍然有几个问题。例如:
#include <iostream>
using namespace std;
class A
{
public:
virtual void PrintA()=0; //1 vtable and 1 vpointer
};
class B
{
public:
virtual void PrintB()=0; //1 vtable and 1 vpointer
};
class Parent: public A, public B
{
public:
void PrintA();
void PrintB(); // 3 vtables and 3 vpointers, right?
virtual void PrintChild()=0;
};
void Parent::PrintA()
{
cout<<"A";
}
void Parent::PrintB()
{
cout<<"B";
}
class Child: public Parent
{
public:
void PrintChild(); //3 vtables and 3 vpointers
};
void Child::PrintChild()
{
cout<<"Child";
}
int main()
{
Parent* p1= new Child();
p1->PrintChild();
delete p1;
return 0;
}
问题1:父母和孩子会有3个vtable和3个vpoint吗?
问题 2:p1 如何知道要使用哪个 vpoint?我听说这取决于编译器,但我只是想有人澄清一下。
是的,明确的答案取决于编译器。 甚至不能保证虚拟调度将在 vtables 中实现。
通常,编译器将遵循特定平台的 ABI。 在许多系统上,GCC 实现了为 IA-64 发明的特定 ABI,但随后被移植到其他系统。 这在网上很容易获得,有来自海湾合作委员会网站的链接。
至少在Linux上,在实践中了解更多关于vtables的一种方法是使用gdb
,用-g
编译一个小的示例程序,并使用info vtbl
来探索vtable。 但是,由于涉及虚拟析构函数调试信息的 GCC 错误,这目前有点棘手;因此,请确保始终使用析构函数以外的方法。
我编译了您的程序,并在初始化p1
后停止gdb
。 然后:
(gdb) info vtbl p1
vtable for 'Parent' @ 0x400a10 (subobject @ 0x602010):
[0]: 0x400806 <Parent::PrintA()>
[1]: 0x400810 <Parent::PrintB()>
[2]: 0x400824 <Child::PrintChild()>
vtable for 'B' @ 0x400a38 (subobject @ 0x602018):
[0]: 0x40081a <non-virtual thunk to Parent::PrintB()>
在这里,你可以看到Parent
和Child
实际上只有两个vtable,而不是三个。 这是因为在此 ABI 中,单个继承是通过扩展父类的 vtable 来实现的;在这种情况下,A
的扩展以同样的方式处理。
至于p1
如何知道使用哪个 vtable:这取决于用于进行调用的实际类型。
在代码中,调用p1->PrintChild()
,p1
是Parent*
。 在这里,调用将通过您在上面看到的第一个 vtable 进行 - 因为没有其他任何意义,因为PrintChild
没有在 B
中声明。 在此 ABI 中,vtable 存储在对象的第一个插槽中:
(gdb) p *(void **)p1
$1 = (void *) 0x400a10 <vtable for Child+16>
现在,如果您将代码更改为将p1
转换为B*
,则会发生两件事。 首先,指针的原始位将更改,因为新指针将指向完整对象的子对象。 其次,此子对象的 vtable 插槽将指向上面提到的第二个 vtable。 在这种情况下,有时会对子对象应用特殊偏移以再次查找完整对象。 使用继承时,还有一些特殊的调整virtual
适用(这使对象布局有点复杂,因为有问题的超类在布局中只出现一次)。
您可以看到如下所示的这些更改:
(gdb) p (B*)p1
$2 = (B *) 0x602018
(gdb) p *(void**)(B*)p1
$3 = (void *) 0x400a38 <vtable for Child+56>
这几乎都是Linux上常用的ABI特有的。 其他系统可能会做出不同的选择。
- 虚拟决赛作为安全
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- 如何在C++中获得"静态纯虚拟"功能?
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 大小虚拟继承中的派生类
- 链接器找不到在虚拟类 c++ 中访问的静态字段的符号
- 使用 C++ 和 i2c 工具从虚拟 i2c 写入和读取
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 来自 DLL 的函数调用 [表观调用的括号前面的表达式必须具有(指向-)函数类型]
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- std::is_trivially_copyable_v 关于虚拟功能
- 删除C++继承中虚拟类成员的代码重复
- 子类地址等于虚拟基类地址?
- 当覆盖存在时调用基本虚拟"binded to object"函数
- C++中的多个虚拟表格和虚拟观众