为什么vptr被存储为具有虚拟函数的类的内存中的第一个条目
Why is vptr stored as the first entry in the memory of a class with virtual functions?
对于某些编译器,如果类具有虚拟函数,则可以使用其对象的第一个字节的地址访问其vptr。例如,
class Base{
public:
virtual void f(){cout<<"f()"<<endl;};
virtual void g(){cout<<"g()"<<endl;};
virtual void h(){cout<<"h()"<<endl;};
};
int main()
{
Base b;
cout<<"Address of vtbl:"<<(int *)(&b)<<endl;
return 0;
}
我知道它依赖于不同的编译器行为。既然vptr是作为第一个条目存储的,那么这样做的好处是什么?这是否有助于提高性能,或者仅仅是因为使用&b
这是一个实现细节,但实际上许多实现都是这样做的。
它相当高效和方便。假设您需要为给定的对象调用一个虚拟函数。您有一个指向该对象和虚拟函数索引的指针。您需要找到应该使用该索引和该对象调用的函数。好吧,您只需访问指针后面的第一个sizeof(void*)
字节,找到vtable所在的位置,然后访问vtable的必要元素以获取函数地址。
您可以为每个对象存储一个单独的"vtable for each object"映射或其他东西,但如果您决定将vptr存储在对象内部,那么使用第一个字节,而不是最后一个字节或任何其他位置是合乎逻辑的,因为使用这种方法,一旦您有了指向对象的指针,就知道在哪里可以找到vptr,而不需要额外的数据。
尽管这是定义的实现,但似乎没有太多真正的选择。
首先,我们可以看到,以太必须有一个vptr
或一个嵌入的vtable
。后者意味着您将不得不在构造时复制vtable
,并且它会消耗更多内存,但其优点是避免在每个方法调用上取消引用一个指针。根据具体情况,这两种方法可能都有很好的论据——大多数实现都选择了降低构建时间和总体内存消耗,而不是节省调度时间。
当选择vptr
方法时,我们看到我们必须保持基类和派生类布局的二进制兼容性。首先,我们可以通过(经常)使用一个vptr
来实现这一点,出于兼容性的原因,这个vptr
必须生活在最基本的类中。
当处理简单继承时,在派生类到基类之间转换的最直接的方法是保持指针值,这意味着布局必须首先是基类的字段,然后是派生类的添加。
现在我们已经很接近为什么把vptr
放在第一位了。它只需要靠近对象的起点,因为它必须生活在对象最基本的部分。
那么,我们把它放在偏移量0上的原因可能是它是一个对所有类都可用的一致偏移量。您根本无法保证有任何数据可以放在vptr
之前。
将CCD_ 10置于偏移0处也具有一些优点。如果您知道对象具有vptr
,那么您就知道您必须查看偏移量0,而不需要知道对象的类型(比它具有vptr
更多)。这可以方便地用于某些调试目的(vtable
通常包含足够的信息来推断实际类型)。特别是这使得typeid
和类似的代码实现起来更简单,因为您只需要查看相同的偏移量就可以通过预定义的偏移量检索type_info
节点,这意味着您可以共享typeid
的实际代码。
- 为什么它只打印双链接列表的第一个值,而我的程序却崩溃了
- std::find,返回所有找到的值的替代方法,而不仅仅是存在重复的向量的第一个值
- 如何仅读取文本文件中的第一个值
- 在C++中,如何在第一个"system()"结束后执行第二个"system()"?
- 查找不在标准中的第一个值::设置<int>最小-最大值
- C++:忽略第一个 cin.ignore 之后的输入
- 在C++中打印多个矢量的第一个值
- C++去除前x个元素的有效方法,在不改变向量大小的情况下将第x+1个元素推到第一个
- C++第一个cout将不会打印
- 我们可以在第一个else-if条件结束后使用另一个else-if条件吗
- OpenGL:第二个VBO破坏了第一个VBO
- 为什么第一个Dynamic_cast没有投射到基类?
- OpenGL 2D游戏只绘制第二个精灵纹理而不是第一个
- C++ 为什么程序只读取第一个值
- 在我的第一个C++程序中需要一些帮助(简单)
- 为什么我的代码在第一个 if 语句处中断?
- 是否可以从另一个类对象调用一个类函数而不继承第一个类
- 无法使我的第一个Windows OpenGL窗口抬起并运行
- 将参数初始化为构造函数,而不是第一个
- 无法在硬件模式下创建 SGX 安全区 - "invalid launch token"即使文档将无效的启动令牌指定为第一个