4

对于一些编译器,如果一个类有虚函数,那么它的 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 更容易访问 vbtl?

4

2 回答 2

4

这是一个实现细节,但实际上许多实现都是这样做的。

它相当高效和方便。假设您需要为给定对象调用虚函数。您有一个指向该对象和虚函数索引的指针。您需要以某种方式找到应该使用该索引和该对象调用哪个函数。好的,您只需访问sizeof(void*)指针后面的第一个字节并找到 vtable 所在的位置,然后访问 vtable 的必要元素以获取函数地址。

您可以存储“每个对象的 vtable”或其他内容的单独映射,但是如果您决定要将 vptr 存储在对象内,那么使用第一个字节而不是最后一个字节或任何其他位置是合乎逻辑的,因为有了这个一旦你有一个指向对象的指针,你就知道在哪里可以找到 vptr,不需要额外的数据。

于 2015-11-06T07:33:02.533 回答
3

尽管这是定义的实现,但似乎没有太多真正的选择。

首先,我们可以看到你的 ether 必须有一个vptr或一个嵌入的vtable. 后者意味着您将不得不复制vtableon 构造并且它会消耗更多内存,但具有避免在每个方法调用上取消引用一个指针的优点。根据具体情况,它们可能都有很好的论据——大多数实现选择降低构建时间和整体内存消耗,而不是节省调度时间。

When chosen vptrapproach we see that we must keep binary compatibility of the layout of base and derived classes. 首先,我们可以通过(经常)使用 one 来实现这一点,出于兼容性原因vptr,这vptr必须存在于最基本的类中。

在处理简单继承时,在派生类到基类之间进行转换的最直接方式是保留指针值,这意味着布局必须首先是基类的字段,然后是附加派生类对其做出贡献。

现在我们已经很接近为什么要vptr放在第一位的原因了。它必须位于对象的开头附近,因为它必须位于对象的最基本部分中。

那么我们将它放在偏移量 0 上的原因可能是它是可用于所有类的一致偏移量。您根本无法保证有任何数据可以放在vptr.

vptrat 偏移量设为 0 也有一些优势。如果您知道对象具有 a vptr,您就知道必须查看偏移量 0 而无需知道对象的类型(比它具有 a 更多vptr)。这对于某些调试目的很方便(vtable通常包含足够的信息来推断实际类型)。尤其是这使得typeid和类似的实现更简单,因为您只需查看相同的偏移量即可type_info通过预定义的偏移量检索节点 - 这意味着您可以共享typeid.

于 2015-11-06T07:33:36.670 回答