`
weiyinchao88
  • 浏览: 1184838 次
文章分类
社区版块
存档分类
最新评论

c++多重继承和虚继承及虚函数深入理解

 
阅读更多

先看一例子:
class base
{
public:
virtual void f() const {};
};

class d1 : virtual public base
{};

class d2 : virtual public base
{};

class derived : public d1, public d2
{};
using namespace std;
int main()
{
d1 b;
d2 c;
derived d;
cout<<sizeof(b)<<endl;
cout<<sizeof(c)<<endl;
cout<<sizeof(d)<<endl;
return 0;
}
结果是 8, 8 12.
先说class Base吧,它有一个虚函数,且没有父类,所以只有一个指针大小,也就是4BYTES了.
至于d1和d2就不那么简单了,它不仅有虚函数而且是虚继承.要复杂一些了.
D1之所以为8是由于它有两个表指针组成的.
一个PVBTBL,一个是PVFTBL.
PVBTBL是指虚基类表指针,一个类可以有0-n个VBTBL(VIRTUAL BASE CLASS TABLE),在这里只有
一个,不过很快就会看到有两个VBTBL的类了。
PVFTBL是指虚函数表指针了,一个类可以有 0-N个VFTBL(VIRTUAL FUNCTION TABLE),在这里也只有一个。我们看一看D1的布局吧。
D1
---------------
PVBTBL ---------------------》指向一个虚基类表。
--------------
PVFTBL ---------------------》指向一个虚函数表。
--------------
所以其大小就为8了。
再说说其初始化过程。我们知道任何一个类的虚基类的构造函数是最先被调用。
最开如,编译器会把d1::vbtbl的指针表放在图中PVBTBL的地方,此时THIS指针也指向PVBTBL,
然后调整THIS指针令其指向PVFTBL,并调用BASE::BASE();
BASE()会在PVFTBL的地方放上OFFSET BASE::VFTBL,由于它没有数据成员要初始化,所以它就返回了。呵呵,很不幸,它很快就要被D1::的VFTBL所代替了,这也正是为什么C++会根据实际对象的类型所进行正确行为的原因。不过,这里的替代有点儿特别。
它先查看,VBTABL(其中第二项为虚基类的偏移[PVBTABL+4]),找到虚基类的偏移量,然后再加上THIS指针的值,些时它正好指向PVFTBL,它就用D1::VFTBL替代了虚基类的虚函数表了。
上边提到有的类会有两个虚基表,-DEPRIVED类就是这种情况了。它的初始化比上面所说的更复杂。
先看布局也再说吧
----------
PVBTBL ------》同上
----------
PVBTBL ------》同上
----------
PVFTBL--------》同上
----------
PVFTBL--------》同上。
---------------------
很明显可以看到它有两个虚基类表,ONE FOR D1,ANOTHER FOR D2。
虚基类表的个数取决于当前的继承方式及其基类的情况。
在这里可以看到,它的基类D1,D2都有虚继承的情况,所以这里就有两个虚基类表了。
INIT:
在这里编译器会先准备两个VBTBL,然后,当初始化时(也就是调用CONSTRUCTOR时),两个VBTBL分别放到相应的位置,然后调整THIS指针的值,THIS+8,POINTING TO THE FIRST PVFTBL的位置,然后调用BASE::BASE(),可以看到的确是虚基类的构造函数是最先调用的。
它会把它的OFFSET BASE::VFTBL,放到this + 8 的位置,没有其它要初始化的成员,SO,RETURN TO THE CALLER。
THEN THIS= THIS -8,INVOKE THE D1::D1(),
ENTER THE D1::D1(),FIRSTLY ,IT LOOKS UP THE VBTBL POINTED BY FIRST PVTBL FOR THE OFFSET THE ITSELF,,然后,修改THIS的值,令其指向,SUBOBJECT OF D1 ,由于这里也没有成员,所以初始化也很简单,它只是用它自己的虚函数替代上面有BASE::BASE()所放置的虚函数表,然后,返回。至于后面的D2,也和这里类似,不过THIS指针所指的位置不同,它指向上图中的最后一个位置。
终于到最后一步了,DEPRIVED会用它自己的虚函数表去替代原来已有的。。。。。(反正自己看,不用说太清了。)
下面还有更好玩的。一个类可能会有多个虚函数表,且不同的。
看这个例子。
#include "stdafx.h"
#include <string.h>
class Base1
{
public:
virtual void Base1Test() {printf("this is from Base1 virtual function /n");}
int a;

};

class Base2
{
public:
virtual void Base2Test() {printf("this is from the Base2 virtual function/n");}
int b;
};
class Deprived : public virtual Base1, public Base2
{
public:
int d;
};
Base1 * base1;
Base2 *base2;
void f()
{
Deprived deprived;
base1 = &deprived;
base2 = &deprived;
base1->Base1Test();
base2->Base2Test();
deprived.a = 16;
deprived.b = 32;
deprived.d = 48;
return;
}
int main(int argc, char* argv[])
{
printf("sizeof(Deprived):%d/n", sizeof(Deprived));
f();

return 0;
}
NOTE:本例中,只有一个VBTBL,呵呵,由于DEPRIVED VIRTURE INHERITS THE BASE1,
但是它却有多个虚函数表。ONE FOR BASE1, ANOTHER FOR BASE2。
它的内存布局和上面有较大变化了。如下所示。
DEPRIVE
----------------
BASE2 PARTY =========》 INCLUDE 一个虚函数表指针及int b(它就是BASE2的成员变量。
----------------
VBTBL PARTY ==========》就是一个指向虚基类表的指针了。不用多说了。
-----------------
DEPRIVE PARTY ==========》这里仅有一个DEPRIVED的成员变量d,它和BASE2共用一个虚函数表指针。所以就只有一个成员变量D了。
-----------------
VIRTUAL BASE PARTY -=======》也就是BASE1 PARTY了。它结构如下。
===========
PVFTBL --------------》BASE1的VIRTUAL FUNC TBL
=============
-------------------------------
理所当然它会所调用BASE1的CONSTRUCTOR,(FOR THE VIRTUAL BASE REASON。。。。),
,然后正常初始化,不过到了最后,会比较独特,DPRIVED CLASS有两个VIRTUAL TABLE。
一个是为BASE1准备的,一个是为BASE2准备的。
也正是为什么可以用基类的指针正确的调用子类的VIRTURL FUNCTION的原因。。。。。
更详细,就不说了,反正是自己看。呵呵。
分享到:
评论

相关推荐

    C++继承,虚函数与多态性专题

    同名变量和函数问题,最后介绍了多重继承与虚基类。本文第二部分重点介绍了虚函数与多态性的问题,因此学习虚函 数的基础是继承,因此在学习虚函数前应学好继承。本文详细易懂,内容全面,是学习C++的不错的资料。

    (转)多重继承下的虚函数表

    对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为VFTable。 在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应...

    类的继承第二次作业(多重继承与虚基类)参考答案_C++_teacher_

    定义一个Person 类,结构如下图,为该类添加输入输出流运算符重载。Person 类以Public方式派生出一...char* title(职称),为该类添加输入输出流运算符重载,完成有参、无参、拷贝构造函数的定义,在主函数测试Teacher类

    C++面向对象技术完全剖析_源代码(继承,封装,多态,虚函数,纯虚函数,虚拟继承,多重继承,函数重载,指针……)

    要求:1、虚函数 多态 多态表现为 基类 基类指针和继承间的关系 2、带有多对象成员。定义 3、体现继承 虚拟继承(要通过至少三层 父类父类子类) 虚函数 (3层 纵向关系) 水平方向上:体现出继承顺序 先虚拟继承 ...

    关于C++中菱形继承和虚继承的问题总结

    本文将给大家详细介绍关于C++菱形继承和虚继承的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍吧。 继承:  1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承  2. 多继承–一...

    C++ 编程思想 象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器类、多重继承、异常处理和运行时类型识别

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++ 多重继承和虚拟继承对象模型、效率分析

    通过带有虚函数的单一继承我们可以清楚的理解继承的概念、对象模型的分布机制以及动态绑定的发生,即可以完全彻底地理解多态的思想。为了支持多态,语言实现必须在时间和空间上付出额外的代价(毕竟没有免费的晚餐,...

    多重继承--虚基类工程代码

    且构造函数和析构函数的调用顺序和单继承是一样的,先调用基类构造函数,再调用对象成员的构造函数,最后调用派生类的构造函数。那么处于同一层次的各个基类构造函数的调用顺序是取决于声明派生类时所指定的各个基类...

    多重继承机制.vsdx

    《C++对象模型探索 —— 多重继承虚函数表分析》博客的画图原文档。

    多重继承及虚继承中对象内存的分布

    这篇文章主要讲解G++编译器中虚继承的对象内存分布问题,从中也引出了dynamic_cast和static_cast本质区别、虚函数表的格式等一些大部分C++程序员都似是而非的概念。本文是介绍C++的技术文章,假定读者对于C++有比较...

    c++继承中的内存布局

    1* 类如何布局? 2* 成员变量如何访问? 3* 成员函数如何访问? 4* 所谓的“调整块”(adjuster thunk)是... * 单继承、多重继承、虚继承 * 虚函数调用 * 强制转换到基类,或者强制转换到虚基类 * 异常处理

    C++编程思想,进一步理解c++

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++- 抽象类(存虚函数)、接口、多重继承1

    这是因为Derived d对象地址里依次存了两个不同的父类成员变量值,如下图所示: 从上图看到,其实pa和pb还是位于d对象地址里,只是指向的位置不同而已.所以

    Thinking in C++ 英文版

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    vs2008 多重继承虚基类的简单应用

    虚基类的简单应用举例,分别把不同类的声明放在不同的头文件中,把不同类的实现放在不同的源文件中,把类的声明和成员函数的定义进行分离,初步实现了C++在实际工作中的编程思想,信息屏蔽。

    Thinking c++中文版

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    c++编程学习及快速入门

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++编程思想与深入解析

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    Thinking in C++中文版(C++编程思想)

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

Global site tag (gtag.js) - Google Analytics