vptr - 虚拟表

时间:2009-12-02 09:05:33

标签: c++ class polymorphism

还有一些我还没有得到的东西。

对于每个类,我声明有一个隐藏的vptr成员指向类虚拟表。

让我说我有这个声明:

class BASE
{
    virtual_table* vptr; //that's hidden of course , just stating the obvious
    virtual void foo();
}

class DERIVED : public BASE
{
   virtual_table* vptr; //that's hidden of course also
   virtual void foo();
   virtual void cho();
}

首先我想了解一下,对于派生和基础,它是否与vptr的成员名相同?

第二,在这种情况下会发生什么:

base* basic = new derived();

我明白了,基本变量得到派生的vptr,但是这是怎么回事?通常在转换发生时,派生的基本部分(包括base的vptr)应该分配给basic,而不是派生的vptr。也许它是不同的,如果在两个类中都有一个具有相同名称的变量,我不知道。

第三个也是最后一个问题: 当我有

 base* basic = new derived();

有没有办法用basic-base的成员函数调用,即使它是虚拟的?

感谢

8 个答案:

答案 0 :(得分:2)

首先,是的,它是同一个成员。它在运行基础构造函数时首次自动分配,并在运行派生构造函数时第二次分配。 (在默认的空构造函数的情况下,基础的无用的分配被优化掉了。)

第二,没有真正的转换。实际上,推导可以被命名为“是一种”关系。在这种情况下,派生的“是”基础。如果考虑派生对象的第一个内存字节,它们的含义与基础对象的第一个字节相同。

第三,您可以按如下方式调用基本成员函数:basic->base::foo();

答案 1 :(得分:2)

首先,虚拟表不是C ++标准的一部分。 C ++编译器可以任意方式自由地实现虚函数。通常他们会使用虚拟表,true;但在这种情况下,他们可以以他们认为合适的方式实施它们。

在大多数通常的实施中,basic未获得derived的{​​{1}}; vptr的{​​{1}}将指向*basic的{​​{1}},这实际上并不相同。 vptr只是一个指针,指向的对象有一个derived。不涉及转换。无论如何,vtable只是实现细节的内部名称。没有名为basic的真正的班级成员。

你总是可以通过使用类名(在你的情况下为vptr)对它进行限定来调用任何基类函数。

更新:仅仅为了记录,我尝试在VC ++ 2008中使用一个名为vptr的指针创建一个类(这是该编译器中vptr的内部名称),它按预期工作,即使调试器对变量名称感到有点困惑。

答案 2 :(得分:1)

vtablevirtual_table,在您的话中)将指向BASE的不同地址,DERVIVED提供DERIVED覆盖至少一个虚拟函数BASEBASE的每个实例都会有相同的vtable指针(同上DERIVED)。

当您执行以下操作时:

someobject->foo();

这被翻译为:

someobject->vtable[COMPILER_GENERATED_OFFET_FOR_FOO]();

vtable是一个函数指针数组,其中某个虚函数的偏移量(比如foo)对于类层次结构中的所有类都是相同的。

答案 3 :(得分:1)

3)。你可以.base * basic = new derived();

basic-> BASE :: foo的(); //将调用基类方法并在编译时解析。

2)。当类中没有虚拟东西时(如果派生类不覆盖任何基类函数),但是如果派生类具有虚函数(覆盖),那么可以实现运行时多态,这对于编译器初始化基类的vptr是必要的。 vptr of derived,它会增加该代码。

1)。是的,接口函数原型必须是相同的其他基类函数被派生类函数隐藏(例如,如果参数在派生类函数中延迟)。

答案 4 :(得分:1)

关于第二个问题。 (这在标准中没有说明,并且可以通过其他方式实现,所以只需要从中获取一般性的想法。对于单继承层次结构,它也过于简化,多重继承使一切变得更加复杂)。

派生基础对象和基础对象的内存布局与基础对象的大小(包括编译器注入的任何数据)的重合程度为100%。这意味着具有base类型且实际指向derived的指针实际上将指向可以解释为base对象的一块内存,即使内容(vptr值)不同。

base          derived

base_vptr     base_vptr
base_attrs    base_attrs

              derived_vptr
              derived_attrs

当您创建derived的实例时,编译器将调用相应的derived构造函数,其初始化列表通过调用base构造函数开始。此时,vtable指针base_vptr被设置为指向基类的虚拟表,因此所有指针都引用base::methodbase构造函数完成后,base_vptr在derived中更新,并设置为指向derived vtable,因此如果方法为derived::method,则实例指向derived覆盖derived_vptr。此时derived指向derived::new_method中添加的虚拟方法的派生vtable,并指向base2 ...

只是为了说明一点:vtable不一定存储指向实际方法的指针。在某些情况下,只要调用虚方法,就必须执行中间代码,并且每当多重继承发挥作用时就会发生这种情况。事情变得更复杂,因为派生类只能与其基础之一(通常是声明的第一个)对齐。事情真的很棘手。向base2的向上转换会修改指针,使其指向可以直接解释为base2实例的内存位置,因此derived类型的指针(内存位置)的内容指向类型为derived的对象与类型为base2的指针不会与同一对象重合。此时,如果调用来自this的虚方法,系统必须执行一些魔术来重新计算传递给derived::method_from2虚方法的隐式derived参数的正确位置(必须指向整个base2对象,而不仅仅是{{1}}子对象。

答案 5 :(得分:0)

从派生子项调用父方法:old question

答案 6 :(得分:0)

首先:是:每个实例都会获得基地声明的成员变量。

第二次:在这种情况下,您只能访问基类的成员。

第三:如果它是虚拟的,你可以调用它,只要它不是纯虚拟的(这意味着它没有任何实现,它的声明如下:virtual void foo() = 0;

答案 7 :(得分:0)

您可以查看vtable实施细节here;这就是GNU编译器和AFAIK其他主要的Unix编译器所使用的。特别是“Vtable布局”示例页面具有指导意义。

正如其他人所说,这是一个实现细节,C ++标准本身就此问题保持沉默。