还有一些我还没有得到的东西。
对于每个类,我声明有一个隐藏的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的成员函数调用,即使它是虚拟的?
感谢
答案 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)
vtable
(virtual_table
,在您的话中)将指向BASE
的不同地址,DERVIVED
提供DERIVED
覆盖至少一个虚拟函数BASE
。 BASE
的每个实例都会有相同的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::method
。 base
构造函数完成后,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 ++标准本身就此问题保持沉默。