class A
{
public:
int a;
virtual void f1(){}
virtual void f2(){}
};
class E : public A
{
public:
void virtual f3(){}
};
class F : virtual public A
{
};
class G : virtual public A
{
void virtual f4(){}
};
int main()
{
cout<<sizeof(A)<<endl;//8
cout<<sizeof(E)<<endl;//8
cout<<sizeof(F)<<endl;//12
cout<<sizeof(G)<<endl;//12
}
如上,私以为:
1、显然,A中因为虚函数的存在,增加了一个指向虚表的指针,所以大小为4(int a)+ 4(指针)=8
2、从E的大小可以看出,一般派生时,派生类中增加虚函数并没有导致派生类变大,这说明派生类 和基类应该是公用了同一张虚函数表,他们的虚函数地址都放在里面,所以没有必要在派生类中 同样增加一个指针指向虚表。因此E的大小不变。
3、从F的大小可以看出,虚继承时,派生类中会增加一个指针指向他的父类,因此派生类的大小增 加4字节。
4、class G 是为了进一步印证2、3点。
所以我是这么总结的:
1、如果基类、派生类均含有虚函数,他们是公用一张虚表的
2、虚继承会在派生类中额外增加一个指向父类的指针
然而,有这么一条博文说基类派生类有各自不同的虚表,和我的推论相悖,但却视乎证据确凿,我竟无言以对,所以凌乱了。求各位指点啊。http://blog.csdn.net/kangroger/article/details/38313461
1
ini 2015-06-22 12:48:33 +08:00
|
2
ilotuo 2015-06-22 13:25:00 +08:00 via Android
应该是继承的时候复制了基类的虚函数表吧。
每个类有自己的虚函数表,只不过根据有没有重新实现的话改变里面函数指针的值。 有本书叫 深入探索cpp对象模型 |
3
josephpei 2015-06-22 14:04:30 +08:00
Lippman 《Inside C++ Object Model》深度探索C++对象模型,有详细解释。
|
4
codercai OP @ini
非常感谢层煮的分享! 博主的文章基本能看懂,那么问题还是存在的呀,如果确实是基类派生类各自有其虚表,那么也就是个自有一个虚表指针,这样的话,上面的输出就说不通了,应该是 cout<<sizeof(E)<<endl;//12。 这个作何解释呀? |
5
secondwtq 2015-06-22 18:11:58 +08:00
@codercai 我细节很多地方都忘了,不过这块还是有点记忆的。
无论是基类还是派生类的对象实例,都只会有一个 vptr,占据对象布局中相同的位置。 所不同的只是其指向的虚表。 你想啊,加一个继承层次,就在其所有实例对象上加一个 vptr,这样做,和所有需要 vtable 的实例对象都有且只有一个 vptr,哪个成本低。 况且还有一个关键问题就是,如果子类实例对象有多个 vptr(单继承),当你使用基类指针调用虚函数的时候,是根本没有办法判断该指针所指对象是不是拥有楼主所推断出的“多出来”的那个 vptr 的。 实际情况是,基类和派生类的对象实例,都有且只有一个 vptr,并占据对象布局中相同的位置(当然是有虚函数的时候)。而在不同类型的对象实例中,这个指针指向不同的 vtable。 举个栗子,如果你知道了不同类型对象虚表的地址,在你对 C++ 搞出来的可执行程序做动态汇编调试的时候,找到一个对象的位置,那么在 hex view 里面瞄一眼这个对象的开始几个字节(vptr 的位置),就能知道这个对象是什么类型,我管这个叫人肉 RTTI :) C++ 运行期多态的精髓,窃以为就在这个虚指针上。另外我个人一般是把这个 vptr 当作一个 data member 来看的。 |
6
secondwtq 2015-06-22 18:19:13 +08:00
@codercai 另外,之所以基类和派生类不共用同一张虚函数表,我个人认为是因为同一个基类可能会派生出多个子类,并且它们可能会增加、覆盖不同的虚函数。
而在进行虚函数调用的时候,你所拥有的信息只有:一个 vptr 及该 vptr 指向的 vtable;类型定义;指针/引用的类型(不一定是对象实际类型,但是就算不是也一定是其基类);由类型信息所推导出的,编译器写入可执行代码的一个索引。 这些信息,除了第一项是运行时的,后三项全部是编译时确定的。我觉得如果共用一张 vtable 的话,在以上条件下,是无法处理第一段所描述的复杂情况的。 |
7
codercai OP @secondwtq
又查了一些资料,加上层所言,基类派生类确实是各有其虚表,那么问题就是更奇怪了,为什么输出是这样的呢? cout<<sizeof(A)<<endl;//8 cout<<sizeof(E)<<endl;//8 然而不应该是 cout<<sizeof(A)<<endl;//8 cout<<sizeof(E)<<endl;//12 这样的么? 都应该有一个指向虚表的指针啊~~~ |
8
secondwtq 2015-06-23 01:51:16 +08:00
@codercai 你也许把“子类实例是子类的父类部分与子类部分所拼接起来的”和这个搞混了
子类和父类实例使用不同的 vtable,其 vptr 的值也相应是不一样的。但是每个实例中只有一个 vptr(对于单继承来说是这样的)。 可以这样说,vtable 不能简单被分为“基类”和“子类”两个部分,一个对象的 vptr 所指 vtable,其中包含了该对象实际类型中所有可以用指针/引用直接调用虚函数(包括父类中未被子类 override 掉的,子类新定义的,和子类 override 掉父类的)。因此任何一个实例,只有一个 vptr 的位置(依然是只针对单继承)。 我个人平常的理解是,这个 vptr 相当于对象的一个“类型信息”或者类型的 ID,据我所知某些动态语言的实现中,对象实际在内存中的大小是不定的,但是每个对象头部的结构是确定的,这个头部中存储了必要的类型信息,据此可推导出对象的具体“类型”,以及该对象究竟符合哪一种布局。 C++ 中一个实例对象的实际类型可能有好几层继承,涉及到若干个基类,但是其类型总是能唯一确定的。 |