精华内容
下载资源
问答
  • 菱形继承中的虚函数表 class A { public: virtual void func1() { cout << "A::func1" << endl; } private: int _a; }; class B :public A{ public: virtual void func1() { cout << "B::func...

    菱形继承中的虚函数表

    class A {
    public:
    	virtual void func1() { cout << "A::func1" << endl; }
    private:
    	int _a;
    };
    
    class B :public A{
    public:
    	virtual void func1() { cout << "B::func1" << endl; }
    	virtual void func2() { cout << "B::func2" << endl; }
    private:
    	int _b;
    };
    class C :public A{
    public:
    	virtual void func1() { cout << "C::func1" << endl; }
    	virtual void func2() { cout << "C::func2" << endl; }
    private:
    	int _c;
    };
    class D : public B, public C {
    public:
    	virtual void func1() { cout << "D::func1" << endl; }
    	virtual void func3() { cout << "D::func3" << endl; }
    private:
    	int _d;
    };
    
    typedef void(*VFPTR)();//虚函数指针
    void Print(VFPTR *arr)
    {
    	for (int i = 0; arr[i] != nullptr; ++i)
    	{
    		printf("第%d个虚函数地址:%0x\n",i, arr[i]);
    		arr[i]();
    	}
    }
    
    int main()
    {
    	D d;
    	VFPTR* arr1 = (VFPTR*)(*(int*)&d);//虚表指针(虚函数指针数组)
    	VFPTR* arr2 = (VFPTR*)(*(((int*)&d)+3));//第二个虚表指针偏移量
    	Print(arr1);
    	Print(arr2);
    	return 0;
    }	
    
    • 如下图,d对象是有两个虚表的
      在这里插入图片描述
    • 两个虚表中都不一定只有一个虚函数,vs在这里是无法看到子类新增加的虚函数。
    • 我们将第一个虚表地址所在的地址加上偏移量即可求得第二个虚表地址的地址。将他们虚表内容打印出来或者运行虚函数。
      在这里插入图片描述
    • 可以看出菱形继承派生类的新增加的虚函数放在第一个继承基类部分的虚函数表中。
    • 子类重写Func1时,把B和C两个类中的虚函数都重写了。

    菱形虚拟继承中的虚函数表

    class A {
    public:
    	virtual void func1() { cout << "A::func1" << endl; }
    private:
    	int _a=1;
    };
    
    class B :virtual public A{
    public:
    	virtual void func1() { cout << "B::func1" << endl; }
    	virtual void func2() { cout << "B::func2" << endl; }
    private:
    	int _b=2;
    };
    class C :virtual public A{
    public:
    	virtual void func1() { cout << "C::func1" << endl; }
    	virtual void func2() { cout << "C::func2" << endl; }
    private:
    	int _c=3;
    };
    class D : public B, public C {
    public:
    	virtual void func1() { cout << "D::func1" << endl; }
    	virtual void func3() { cout << "D::func3" << endl; }
    private:
    	int _d=4;
    };
    
    • 如下图是对象d的结构
      在这里插入图片描述
    • 这样看起来很难受,太乱了,无法具体判断。
    • 我们可以根据菱形继承的模型来推导菱形虚拟继承的结构模型
    • 下图是菱形继承的模型
      在这里插入图片描述
    • 根据上图,我们可以推导出菱形虚拟继承的结构模型如下
      在这里插入图片描述
    • 因为A类中的Func1()函数和_a变量都是冗余的,因此在虚拟继承中都会放在对象组成的最下面,他们同时属于B和C.
    • B和C则通过虚基表指针,通过偏移量找到_a。
    • 至于_a和A::Func1()谁先放在对象组成的后面,我们可以通过内存看见是A::Func1()先放在对象组成的后面。
      在这里插入图片描述
    • 下面我们通过代码来验证一下
    typedef void(*VFPTR)();//虚函数指针
    void Print(VFPTR *arr)
    {
    	for (int i = 0; arr[i] != nullptr; ++i)
    	{
    		printf("第%d个虚函数地址:%0x\n",i, arr[i]);
    		arr[i]();
    	}
    }
    
    int main()
    {
    	D d;
    	VFPTR* vfptr1 = (VFPTR*)(*(int*)&d);//第一个虚表
    	VFPTR* vfptr2 = (VFPTR*)(*(((int*)&d) + 3));//第二个虚表
    	VFPTR* A_Func1 = (VFPTR*)(*(((int*)&d) + 7));//A::Func1()在对象组成后面的函数指针
    	
    	Print(vfptr1);
    	Print(vfptr2);
    
    	printf("A::Func1()地址:%0x\n", (int*)A_Func1);
    	(*A_Func1)();//调用函数
    	
    	return 0}
    

    在这里插入图片描述

    • 我们可以看出来两个虚表中都没有了Func1()的函数指针,也的确是放在对象组成的下面。消除了二义性。
    展开全文
  • 最近被问到一个关于多继承虚函数表的问题,当时回答是可能存在多个虚函数表,应该是顺序排列的,但具体怎么排列还是有些疑惑的,回答的时候到有点儿心虚。之后查了资料,做了简单的实验,可以确定的是对于继承了多个...

    前言

    最近被问到一个关于多继承虚函数表的问题,当时回答是可能存在多个虚函数表,应该是顺序排列的,但具体怎么排列还是有些疑惑的,回答的时候到有点儿心虚。之后查了资料,做了简单的实验,可以确定的是对于继承了多个含有虚函数基类的子类来说,指向虚函数表的指针应该不止一个。

    问题

    虚函数表的问题是从C++多态的概念引出的,要想实现多态有3个条件:

    1. 存在继承:没有继承就没有多态(运行时),在多态中必须存在有继承关系的父类和子类。
    2. 重写函数:父类中需要定义带有 virtual 关键字的函数,而在子类中重写一个名字和参数与父类中定义完全相同的函数。
    3. 向上转型:将父类的指针和引用指向子类的对象。

    满足以上三个条件,当使用父类的指针调用带有 virtual 关键字的函数时,就会产生多态行为。

    实现这种多态表现的核心内容就是虚函数表,对于带有 virtual 关键字的函数地址会被放入一个表格,而在类中会有一个指向虚函数表的指针指向这个表格,表明这个表格属于类的一部分。

    对于父类来说,这个表格中都是自己类的虚函数,而对于子类来说,首先这个虚函数表包含父类中所有的虚函数,当子类重写某个虚函数时就会用子类重写后的函数地址替换原来父类中定义的函数地址,同时在子类的虚函数表中还会包含子类独有的虚函数。

    由此可见虚函数表的不同和复杂性还是体现在子类上,所以之后会分别测试单继承、多继承、菱形继承三种情况下虚函数表的不同,主要看一下虚函数表的个数和内存布局情况。

    测试环境

    首先来说明一下测试环境,测试工具是VS2013,对于int *p; sizeof(p)的结果是4,说明编译环境是32位的,这个对后面查看内存结构非常关键。

    开始测试

    使用VS2013查看类的内存布局非常方便,因为类的大小在编译期间就已经确定了,不用运行就可以通过添加编译选项知道类的大小和布局,而指向虚函数表的指针也会占用类的大小,如果说编译的时候确定了类的大小,那从侧面也说明了在编译期间虚函数表实际上也确定了。

    使用VS2013查看类的布局时,可以在项目的属性页:“配置属性”–>“C/C++”–>“命令行”中输入以下任意一个命令,

    • /d1reportAllClassLayout :这个选项可以在VS的输出窗口显示所有相关联的类结构,因为一些外部类也会显示,最终的内容会非常多,需要自己辨别有用的信息。
    • /d1reportSingleClassLayoutXXX :这个选项只会在输出窗口显示指定的类结构,只需要将XXX替换成想显示的类的名字即可,缺点就是无法同时显示多个想查看的类。

    无虚函数简单类结构

    在查看虚函数表的结构之前,先使用之前的编译参数来查看一下简单的类结构,排除虚函数的干扰,能更清楚的了解类成员在类中的布局情况,有一点需要提一下,成员变量会占用类的大小,但是成员函数不会,如果有虚函数,所有的虚函数会被放入一个表格,而在类中放置一个指向虚函数表的指针,来看一下简单代码:

    class CBase
    {
    public:
        void func() {}
    public:
        int m_var1;
    };
    
    class CDerived : public CBase
    {
    public:
        void func() {}
    public:
        int m_var2;
    };
    

    编译输出的类的内存布局为:

    1>  class CBase    size(4):
    1>      +---
    1>   0  | m_var1
    1>      +---
    1>
    1>  class CDerived  size(8):
    1>      +---
    1>      | +--- (base class CBase)
    1>   0  | | m_var1
    1>      | +---
    1>   4  | m_var2
    1>      +---
    

    从上面的输出内容来看,很清楚的可以看到基类 CBase 的大小 size(4) 占用4个字节,只有一个成员变量 m_var1,在类中偏移量为0的位置,而派生类 CDerived 占用8个字节大小,第一个成员继承自基类 CBasem_var1,在类中偏移量为0的位置,还有一个子类独有的成员变量 m_var2,在类中偏移量为4的位置。

    掌握着这种简单类的查看类结构的方法,接下来开始看一下包含虚函数的类的内存布局。

    包含虚函数的类结构

    查看包含虚函数的类结构相对来说麻烦一点,先来说两个符号,免得一会看见结构发懵,vfptr 表示类中指向虚函数表的指针,通常放在类的起始位置,比成员变量的位置都要靠前, vftable 表示类中引用的虚函数表,在具体分析是还有有一些修饰符,用来表明是谁的虚函数表。

    单继承

    这种情况的下的子类的虚函数表很简单,在该子类的内存布局上,最开始的位置保存了一个指向虚函数表的指针,虚函数表中包含了从父类继承的虚函数,当子类中重写父类虚函数时会将虚函数表中对应的函数地址替换,最后添加上自己独有的虚函数地址,下面上代码分析一下:

    class CBase
    {
    public:
        void func0() {}
        virtual void func1() {}
        virtual void func2() {}
    public:
        int m_var1;
    };
    
    class CDerived : public CBase
    {
    public:
        virtual void func2() {}
        virtual void func3() {}
        void func4() {}
    public:
        int m_var2;
    };
    

    上面这两个类的内存布局情况如下:

    1>  class CBase size(8):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | m_var1
    1>      +---
    1>
    1>  CBase::$vftable@:
    1>      | &CBase_meta
    1>      |  0
    1>   0  | &CBase::func1
    1>   1  | &CBase::func2
    1>
    1>  CBase::func1 this adjustor: 0
    1>  CBase::func2 this adjustor: 0
    1>
    1>
    1>  class CDerived  size(12):
    1>      +---
    1>      | +--- (base class CBase)
    1>   0  | | {vfptr}
    1>   4  | | m_var1
    1>      | +---
    1>   8  | m_var2
    1>      +---
    1>
    1>  CDerived::$vftable@:
    1>      | &CDerived_meta
    1>      |  0
    1>   0  | &CBase::func1
    1>   1  | &CDerived::func2
    1>   2  | &CDerived::func3
    1>
    1>  CDerived::func2 this adjustor: 0
    1>  CDerived::func3 this adjustor: 0
    

    看起来是不是比没有虚函数时复杂多了,不过不要着急,从上到下慢慢分析就好了,这次的基类 CBase 大小是8个字节,首先是{vfptr}这个指向虚函数表的指针,在类中的偏移量是0,接下来是成员变量 m_var1,在类中偏移量是4。

    然后是 CBase::$vftable@ 表示基类 CBase 的虚函数表,其中第一行 &CBase_meta 看起来怪怪的,这里我们不展开(因为我也没弄太懂),应该是和虚函数表相关的元数据,第二行是一个0,看起来是一个偏移量,这里没有偏移,当出现偏移时我们再试着分析(相信我,马上就会出现),第三行内容 &CBase::func1 是自己类的虚函数,前面有一个0,应该是指该虚函数在虚函数表中索引,第四行也是相同的情况。

    接下来出现了两行非常相似的内容,看一下CBase::func1 this adjustor: 0,这句代码中的关键是 adjustor,其实有是一个偏移量,据说涉及到thunk技术,据说“thunk其实就是一条汇编指令,操作码是0xe9,就是jmp,后面紧跟操作数”,这里我们就不展开了,如果后面弄明白了可以单独写一篇总结,到此为止基类的内存结构就分析完了。

    继续看派生类 CDerived,它的大小是12个字节,内部结构首先是 {vfptr} 一个指向虚函数表的指针,偏移量为0,m_var1 是从父类继承的成员变量,偏移量为4,而 m_var2 是自己类独有的成员变量,偏移量是8。

    然后看派生类对应的虚函数表 CDerived::$vftable@,跳过前两行直接看一下后面几个函数,发现只有 func1 是基类的,而函数 func2func3 都是派生类的,出现这种情况的原因是子类重写了函数 func2func3 ,所以用重写后的函数地址替换了从基类继承的虚函数,造成了目前看到的状况。

    最后又出现了两行 adjustor,很奇怪为什么 func1 函数没有 adjustor,貌似这个 adjustor 只对当前类有效,先留个疑问,接下来看一下多继承。

    多继承

    当多个父类中都包含虚函数的时候,和子类关联的虚函数表就不止一个了,这个情况是可以通过使用sizeof(子类)来简单验证的:

    这一部分是在没有VS的情况下预先写下的,本来考虑使用VS展开布局后,这一段就没有什么必要了,但是后来想想还是留着吧,因为这一段使用的g++编译器,64位环境,每个指针占用8个字节,通过不同的环境调试,更加可以证明,多继承下的多个虚函数表的存在性:

    class W
    {
    public:
        long n;
    public:
        void func(){}
    };
    

    对于这样的一个简单类,sizeof(W) = 8,类的大小等于成员变量的大小。

    class W1
    {
    public:
        long n1;
    public:
        virtual void func1(){}
    };
    
    class W2
    {
    public:
        long n2;
    public:
        virtual void func2(){}
    };
    

    对于上面这两个简单的包含虚函数的类,sizeof(W1) = 16,sizeof(W2) = 16,因为每个类都除了一个 long 类型的成员变量以外,还包含了指向虚函数的一个指针,所以类的大小是16个字节。

    class WW : public W1, public W2
    {
    public:
        long nn;
    public:
        virtual void func(){}
    };
    

    而继承了 W1W2 这两个父类的子类 WW 在继承了两个成员变量 n1n2 之外,还有自己的成员变量 nn,三个变量占用字节24个,而计算类 WW 的的大小 sizeof(W1) = 40,也就是说除了成员变量24个字节,还剩余了16个字节的空间没有着落,我们知道它至少包含一个指向虚函数表的指针,占用8个字节的大小,还剩8个字节没有找到用处,从此处分析应该还有一个指向虚函数表的指针,具体的情况可以看一下内存分布。

    接下来和单继承的分析方法一样,写代码编译查看布局:

    class CBase0
    {
    public:
        void func0() {}
        virtual void func1() {}
        virtual void func2() {}
        virtual void func3() {}
    public:
        int m_var0;
    };
    
    class CBase1
    {
    public:
        void func0() {}
        virtual void func2() {}
        virtual void func3() {}
        virtual void func4() {}
    public:
        int m_var1;
    };
    
    class CDerived : public CBase0, public CBase1
    {
    public:
        virtual void func1() {}
        virtual void func2() {}
        virtual void func4() {}
        virtual void func5() {}
        void func6() {}
    public:
        int m_var2;
    };
    

    上面3个类描述了一个简单的多继承的情况,之所以写这么多函数就是构建一种,既有虚函数覆盖,又有单独不被覆盖的情况,下面展示了这段代码的内存布局。

    1>  class CBase0    size(8):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | m_var0
    1>      +---
    1>
    1>  CBase0::$vftable@:
    1>      | &CBase0_meta
    1>      |  0
    1>   0  | &CBase0::func1
    1>   1  | &CBase0::func2
    1>   2  | &CBase0::func3
    1>
    1>  CBase0::func1 this adjustor: 0
    1>  CBase0::func2 this adjustor: 0
    1>  CBase0::func3 this adjustor: 0
    1>
    1>
    1>  class CBase1    size(8):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | m_var1
    1>      +---
    1>
    1>  CBase1::$vftable@:
    1>      | &CBase1_meta
    1>      |  0
    1>   0  | &CBase1::func2
    1>   1  | &CBase1::func3
    1>   2  | &CBase1::func4
    1>
    1>  CBase1::func2 this adjustor: 0
    1>  CBase1::func3 this adjustor: 0
    1>  CBase1::func4 this adjustor: 0
    1>
    1>
    1>  class CDerived  size(20):
    1>      +---
    1>      | +--- (base class CBase0)
    1>   0  | | {vfptr}
    1>   4  | | m_var0
    1>      | +---
    1>      | +--- (base class CBase1)
    1>   8  | | {vfptr}
    1>  12  | | m_var1
    1>      | +---
    1>  16  | m_var2
    1>      +---
    1>
    1>  CDerived::$vftable@CBase0@:
    1>      | &CDerived_meta
    1>      |  0
    1>   0  | &CDerived::func1
    1>   1  | &CDerived::func2
    1>   2  | &CBase0::func3
    1>   3  | &CDerived::func5
    1>
    1>  CDerived::$vftable@CBase1@:
    1>      | -8
    1>   0  | &thunk: this-=8; goto CDerived::func2
    1>   1  | &CBase1::func3
    1>   2  | &CDerived::func4
    1>
    1>  CDerived::func1 this adjustor: 0
    1>  CDerived::func2 this adjustor: 0
    1>  CDerived::func4 this adjustor: 8
    1>  CDerived::func5 this adjustor: 0
    

    内容很多,前面两个基类 CBase0CBase1 的布局很简单,参照之前的分析很容易看懂,直接从派生类看起吧。

    我们发现派生类 CDerived 中确实有两个指向虚函数表的指针,接下来看一下这两个虚函数表,这个虚函数表和前面遇到的格式一样,除了第一行的元数据,第二行的诡异偏移量0,剩下的虚函数指针有的是从基类继承来的,有的是被当前派生类覆盖的,还有派生类自己独有的。

    而第二个虚函数表就有点意思了,首先是少了 &CDerived_meta 这一行,然后偏移量终于不是0了,而是-8,从派生类 CDerived 的内存布局上来看,以下开始大胆假设,至于小心求证的部分放到以后来做(看自己的进步状态了)。

    第二个指向虚函数表的指针是不是距离类的起始偏移量是8,我猜这个-8的意思就是指的这个偏移量,这个值有可能被后面使用,第二行出现了 &thunk: this-=8; goto CDerived::func2,其中包含 thunk 字样,表示这个 func2 不归我管,你去-8偏移量的那个虚函数表里找一找。

    还有一点你有没有发现 func5 这个函数只在第一个虚函数表中出现,而没有出现在第二个虚函数表中,这也是一个规则,自己独有的虚函数放到第一个虚函数表中,这可能也是为什么只有第一个虚函数表包含元数据行。

    最后一点,我们发现对于函数 func4 来说 adjustor 终于不是0了,而值变成了8,仿佛在说这个虚函数只在偏移量的为8的位置。

    菱形继承

    对于这一部分,并没有太多新的内容,只是简单的菱形继承中,最初的基类在最终的子类中会包含两份,而虚函数的样子并没有太大的不同,接下来简单看一下代码和对应的内存布局即可,因为菱形继承并不被提倡,所以也不用花太多时间来分析这个问题。

    class CSuper
    {
    public:
        virtual void func0() {}
        virtual void func1() {}
    public:
        int m_var;
    };
    
    class CBase0 : public CSuper
    {
    public:
        virtual void func1() {}
        virtual void func2() {}
    public:
        int m_var0;
    };
    
    class CBase1 : public CSuper
    {
    public:
        virtual void func1() {}
        virtual void func3() {}
    public:
        int m_var1;
    };
    
    class CDerived : public CBase0, public CBase1
    {
    public:
        virtual void func1() {}
        virtual void func3() {}
        virtual void func4() {}
    public:
        int m_var2;
    };
    
    1>  class CSuper    size(8):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | m_var
    1>      +---
    1>
    1>  CSuper::$vftable@:
    1>      | &CSuper_meta
    1>      |  0
    1>   0  | &CSuper::func0
    1>   1  | &CSuper::func1
    1>
    1>  CSuper::func0 this adjustor: 0
    1>  CSuper::func1 this adjustor: 0
    1>
    1>
    1>  class CBase0    size(12):
    1>      +---
    1>      | +--- (base class CSuper)
    1>   0  | | {vfptr}
    1>   4  | | m_var
    1>      | +---
    1>   8  | m_var0
    1>      +---
    1>
    1>  CBase0::$vftable@:
    1>      | &CBase0_meta
    1>      |  0
    1>   0  | &CSuper::func0
    1>   1  | &CBase0::func1
    1>   2  | &CBase0::func2
    1>
    1>  CBase0::func1 this adjustor: 0
    1>  CBase0::func2 this adjustor: 0
    1>
    1>
    1>  class CBase1    size(12):
    1>      +---
    1>      | +--- (base class CSuper)
    1>   0  | | {vfptr}
    1>   4  | | m_var
    1>      | +---
    1>   8  | m_var1
    1>      +---
    1>
    1>  CBase1::$vftable@:
    1>      | &CBase1_meta
    1>      |  0
    1>   0  | &CSuper::func0
    1>   1  | &CBase1::func1
    1>   2  | &CBase1::func3
    1>
    1>  CBase1::func1 this adjustor: 0
    1>  CBase1::func3 this adjustor: 0
    1>
    1>
    1>  class CDerived  size(28):
    1>      +---
    1>      | +--- (base class CBase0)
    1>      | | +--- (base class CSuper)
    1>   0  | | | {vfptr}
    1>   4  | | | m_var
    1>      | | +---
    1>   8  | | m_var0
    1>      | +---
    1>      | +--- (base class CBase1)
    1>      | | +--- (base class CSuper)
    1>  12  | | | {vfptr}
    1>  16  | | | m_var
    1>      | | +---
    1>  20  | | m_var1
    1>      | +---
    1>  24  | m_var2
    1>      +---
    1>
    1>  CDerived::$vftable@CBase0@:
    1>      | &CDerived_meta
    1>      |  0
    1>   0  | &CSuper::func0
    1>   1  | &CDerived::func1
    1>   2  | &CBase0::func2
    1>   3  | &CDerived::func4
    1>
    1>  CDerived::$vftable@CBase1@:
    1>      | -12
    1>   0  | &CSuper::func0
    1>   1  | &thunk: this-=12; goto CDerived::func1
    1>   2  | &CDerived::func3
    1>
    1>  CDerived::func1 this adjustor: 0
    1>  CDerived::func3 this adjustor: 12
    1>  CDerived::func4 this adjustor: 0
    

    虚继承

    解决菱形继承的一个常用的办法就是改为虚继承,实际上虚继承中就是将从最基类中继承的公共部分提取出来放在最子类的末尾,然后在提取之前的位置用一个叫做vbptr的指针指向这里。

    之前看到过一种说法:

    虚继承内部实现也相当复杂,似乎破坏了OO的纯洁性

    至于复杂不复杂,看看后面的内存布局就很清楚了,那是相当复杂,其中出现了各种偏移,简单了解下就行了,如果不是维护老代码,谁现在还写这样的结构。

    class CSuper
    {
    public:
        virtual void func0() {}
        virtual void func1() {}
    public:
        int m_var;
    };
    
    class CBase0 : virtual public CSuper
    {
    public:
        virtual void func1() {}
        virtual void func2() {}
    public:
        int m_var0;
    };
    
    class CBase1 : virtual public CSuper
    {
    public:
        virtual void func1() {}
        virtual void func3() {}
    public:
        int m_var1;
    };
    
    class CDerived : public CBase0, public CBase1
    {
    public:
        virtual void func1() {}
        virtual void func3() {}
        virtual void func4() {}
    public:
        int m_var2;
    };
    
    1>  class CSuper    size(8):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | m_var
    1>      +---
    1>
    1>  CSuper::$vftable@:
    1>      | &CSuper_meta
    1>      |  0
    1>   0  | &CSuper::func0
    1>   1  | &CSuper::func1
    1>
    1>  CSuper::func0 this adjustor: 0
    1>  CSuper::func1 this adjustor: 0
    1>
    1>
    1>  class CBase0    size(20):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | {vbptr}
    1>   8  | m_var0
    1>      +---
    1>      +--- (virtual base CSuper)
    1>  12  | {vfptr}
    1>  16  | m_var
    1>      +---
    1>
    1>  CBase0::$vftable@CBase0@:
    1>      | &CBase0_meta
    1>      |  0
    1>   0  | &CBase0::func2
    1>
    1>  CBase0::$vbtable@:
    1>   0  | -4
    1>   1  | 8 (CBase0d(CBase0+4)CSuper)
    1>
    1>  CBase0::$vftable@CSuper@:
    1>      | -12
    1>   0  | &CSuper::func0
    1>   1  | &CBase0::func1
    1>
    1>  CBase0::func1 this adjustor: 12
    1>  CBase0::func2 this adjustor: 0
    1>
    1>  vbi:       class  offset o.vbptr  o.vbte fVtorDisp
    1>            CSuper      12       4       4 0
    1>
    1>
    1>  class CBase1    size(20):
    1>      +---
    1>   0  | {vfptr}
    1>   4  | {vbptr}
    1>   8  | m_var1
    1>      +---
    1>      +--- (virtual base CSuper)
    1>  12  | {vfptr}
    1>  16  | m_var
    1>      +---
    1>
    1>  CBase1::$vftable@CBase1@:
    1>      | &CBase1_meta
    1>      |  0
    1>   0  | &CBase1::func3
    1>
    1>  CBase1::$vbtable@:
    1>   0  | -4
    1>   1  | 8 (CBase1d(CBase1+4)CSuper)
    1>
    1>  CBase1::$vftable@CSuper@:
    1>      | -12
    1>   0  | &CSuper::func0
    1>   1  | &CBase1::func1
    1>
    1>  CBase1::func1 this adjustor: 12
    1>  CBase1::func3 this adjustor: 0
    1>
    1>  vbi:       class  offset o.vbptr  o.vbte fVtorDisp
    1>            CSuper      12       4       4 0
    1>
    1>
    1>  class CDerived  size(36):
    1>      +---
    1>      | +--- (base class CBase0)
    1>   0  | | {vfptr}
    1>   4  | | {vbptr}
    1>   8  | | m_var0
    1>      | +---
    1>      | +--- (base class CBase1)
    1>  12  | | {vfptr}
    1>  16  | | {vbptr}
    1>  20  | | m_var1
    1>      | +---
    1>  24  | m_var2
    1>      +---
    1>      +--- (virtual base CSuper)
    1>  28  | {vfptr}
    1>  32  | m_var
    1>      +---
    1>
    1>  CDerived::$vftable@CBase0@:
    1>      | &CDerived_meta
    1>      |  0
    1>   0  | &CBase0::func2
    1>   1  | &CDerived::func4
    1>
    1>  CDerived::$vftable@CBase1@:
    1>      | -12
    1>   0  | &CDerived::func3
    1>
    1>  CDerived::$vbtable@CBase0@:
    1>   0  | -4
    1>   1  | 24 (CDerivedd(CBase0+4)CSuper)
    1>
    1>  CDerived::$vbtable@CBase1@:
    1>   0  | -4
    1>   1  | 12 (CDerivedd(CBase1+4)CSuper)
    1>
    1>  CDerived::$vftable@CSuper@:
    1>      | -28
    1>   0  | &CSuper::func0
    1>   1  | &CDerived::func1
    1>
    1>  CDerived::func1 this adjustor: 28
    1>  CDerived::func3 this adjustor: 12
    1>  CDerived::func4 this adjustor: 0
    1>
    1>  vbi:       class  offset o.vbptr  o.vbte fVtorDisp
    1>            CSuper      28       4       4 0
    

    总结

    1. 虚函数表是用来实现多态的核心内容。
    2. 多继承很强大但是不要滥用,当多个基类都含有虚函数时,派生类会有多个指向虚函数表的指针。
    3. 忘记菱形继承吧,为了取消二义性引入虚继承,结果造成内存分布复杂而又难以理解,大道至简,回归本质吧!
    展开全文
  • 继承:只有一个基类和一个派生类 class Base { public: virtual void fun1() { cout "Base::func1()" ; } virtual void fun2() { cout "Base::func2()" ; } private:

    这里写图片描述


    单继承:只有一个基类和一个派生类

    class Base
    {
    public:
        virtual void fun1()
        {
            cout << "Base::func1()" << endl;
        }
        virtual void fun2()
        {
            cout << "Base::func2()" << endl;
        }
    private:
        int b;
    };
    class Derive :public Base
    {
    public:
        virtual void fun1()           //重写基类虚函数,实现多态
        {
            cout << "Derive::func1()" << endl;
        }
    
        virtual void fun3()
        {
            cout << "Derive::func3()" << endl;
        }
        void fun4()
        {
            cout << "Derive::func4()" << endl;
        }
    private:
        int d;
    };
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    1. 虚表就是存放虚函数的表。

    2. 主函数中分别定义一个基类对象和一个派生类对象,通过调试窗口可以看到所谓的虚表,如下图(整型b和d未初始化):

    这里写图片描述

    >也许你会有疑问:调试窗口中派生类虚表为什么看不到Derive中的fun3()函数,这是编译器的问题,我所用的是vs2013,在调试的时候确实不见fun3()函数,所以有时编译器的调试窗口显示的也不能完全相信,那有什么办法证明fun3()函数也在派生类虚表里呢?通过打印虚表!

    代码如下:

    typedef void (*FUNC)();        //重定义函数指针,指向函数的指针
    void PrintVTable(int* vTable)  //打印虚函数表
    {
        if (vTable == NULL)
        {
            return;
        }
        cout << "虚函数表地址:" << vTable << endl;
        int  i = 0;
        for (; vTable[i] != 0; ++i)
        {
            printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
            FUNC f = (FUNC)vTable[i];
            f();         //访问虚函数
        }
        cout << endl;
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    void Test1()
    {
        Base b;
        Derive d;
        int* tmp = (int*)(*(int*)&b);     //取到虚函数的地址
        PrintVTable(tmp);
        int* tmp1 = (int*)(*(int*)&d);
        PrintVTable(tmp1);
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    解析:int* tmp = (int*)(*(int*)&b);

    如下图:

    这里写图片描述


    打印虚表:

    这里写图片描述


    >注意:

    不知你是否注意到派生类中还有一个函数:void fun4(); 
    虚函数是为了实现动态多态,是当程序运行到该函数时才会去虚表里找这个函数;而函数的重载实现的是静态多态, 是在程序编译时就能找到该函数地址,而函数:void fun4();不是虚函数,自然不会在虚函数表里。


    这里写图片描述


    class Base1   //基类
    {
    public:
        virtual void fun1()
        {
            cout << "Base1::fun1" << endl;
        }
        virtual void fun2()
        {
            cout << "Base1::fun2" << endl;
        }
    private:
        int b1;
    };
    class Base2  //基类
    {
    public:
        virtual void fun1()
        {
            cout << "Base2::fun1" << endl;
        }
        virtual void fun2()
        {
            cout << "Base2::fun2" << endl;
        }
    private:
        int b2;
    };
    class Derive : public Base1, public Base2  //派生类
    {
    public:
        virtual void fun1()
        {
            cout << "Derive::fun1" << endl;
        }
        virtual void fun3()
        {
            cout << "Derive::fun3" << endl;
        }
    private:
        int d1;
    };
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    调试看结果:

    这里写图片描述

    同样以上面单继承打印虚表函数来打印多继承虚表:void PrintVTable(int* vTable); //打印虚函数表

    void Test1()
    {
        Derive d1;
        int* VTable = (int*)(*(int*)&d1);
        PrintVTable(VTable);
        VTable = (int*)(*((int*)&d1 + sizeof (Base1)/4));
        PrintVTable(VTable);
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    解析:VTable = (int*)(*((int*)&d1 + sizeof (Base1)/4));

    这里写图片描述


    打印多继承虚表如下图:

    这里写图片描述


    这里写图片描述


    先了解什么是菱形继承?

    这里写图片描述 
    这里写图片描述

    下列代码是菱形继承体系:

    class Base          //Derive的间接基类
    {
    public:
        virtual void func1()
        {
            cout << "Base::func1()" << endl;
        }
        virtual void func2()
        {
            cout << "Base::func2()" << endl;
        }
    private:
        int b;
    };
    class Base1 :public Base  //Derive的直接基类
    {
    public:
        virtual void func1()          //重写Base的func1()
        {
            cout << "Base1::func1()" << endl;
        }
        virtual void func3()
        {
            cout << "Base1::func3()" << endl;
        }
    private:
        int b1;
    };
    class Base2 :public Base    //Derive的直接基类
    {
    public:
        virtual void func1()       //重写Base的func1()
        {
            cout << "Base2::func2()" << endl;
        }
        virtual void func4()
        {
            cout << "Base2::func4()" << endl;
        }
    private:
        int b2;
    };
    class Derive :public Base1, public Base2
    {
    public:
        virtual void func1()          //重写Base1的func1()
        {
            cout << "Derive::func1()" << endl;
        }
        virtual void func5()
        {
            cout << "Derive::func5()" << endl;
        }
    private:
        int d;
    };
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    菱形继承其实是一个单继承与多继承的结合。

    这里写图片描述


    下面跟踪Derive对象d的内存布局:

    这里写图片描述

    进一步解析如下图:

    这里写图片描述


    同样以上面单继承打印虚表函数来打印多继承虚表:void PrintVTable(int* vTable); //打印虚函数表

    这里写图片描述


    这里写图片描述


    这里写图片描述


    >1. 先来看下面两个方案:

    这里写图片描述

    主要解析第一种方案:

    vs2003下虚继承的VBPTR及VBTBL: 
    在类中增加一个指针(VBPTR)指向一个VBTBL,这个VBTBL的第一项记载的是从VBPTR 与本类的偏移地址,如果本类有虚函数,那么第一项是FF FF FF FC(也就是-4),如果没有则是零,第二项起是VBPTR与本类的虚基类的偏移值。

    下面这段代码与上面菱形继承(非虚继承)类似:

    class Base
    {
    public:
        virtual void fun1()
        {
            cout << "Base::fun1()" << endl;
        }
        virtual void fun2()
        {
            cout << "Base::fun2()" << endl;
        }
    private:
        int b;
    };
    class Base1 :virtual public Base  虚继承
    {
    public:
        virtual void fun1()          //重写Base的func1()
        {
            cout << "Base1::fun1()" << endl;
        }
        virtual void fun3()
        {
            cout << "Base1::fun3()" << endl;
        }
    private:
        int b1;
    };
    class Base2 :virtual public Base  //虚继承
    {
    public:
        virtual void fun1()       //重写Base的func1()
        {
            cout << "Base2::fun1()" << endl;
        }
        virtual void fun4()
        {
            cout << "Base2::fun4()" << endl;
        }
    private:
        int b2;
    };
    class Derive :public Base1, public Base2
    {
    public:
        virtual void fun1()          //重写Base1的func1()
        {
            cout << "Derive::fun1()" << endl;
        }
        virtual void fun5()
        {
            cout << "Derive::fun5()" << endl;
        }
    private:
        int d;
    };
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    1. 详细地分析一下vs2003下虚继承的VBPTR及VBTBL:

    以Base1 b1;为例子,详细分析内存布局如下(Base2和Base1的内存布局相似):

    sizeof(Base1) = 20;(下图中黑色区域中所有变量所占的大小)

    这里写图片描述


    当在主函数中定义两个对象Base1 b1和Base2 b2时,还可通过调试进一步探索其内存布局如下:

    这里写图片描述



    最后,我们再来探索一下 Derive d 的内存布局,首先我们先通过调试窗口来跟踪如下:

    这里写图片描述


    通过上面调试窗口可能没办法了解全部,那么请看下图所示:

    sizeof(Derive) = 36;(下图黑色区域所有变量的大小)

    这里写图片描述

    我们怎么验证菱形继承(虚继承)的内存布局就是这样的呢?我们可以通过打印虚表!!

    typedef void(*FUNC)();
    void PrintVPTR(int* VPTR)                   //打印虚表(虚函数)
    {
        cout << "虚函数表地址:" << VPTR << endl;
        for (int i = 0; VPTR[i] != 0; ++i)
        {
            printf("第%d个虚函数地址:0X%x->", i, VPTR[i]);
            FUNC f = (FUNC)VPTR[i];
            f();
        }
        cout << endl;
    }
    void PrintVBPTR(int* VBPTR)                  //打印偏移地址与值
    {
        cout << "虚函数表地址:" << VBPTR << endl;
        int i = 0;
        printf("与本类的偏移地址:0X%x\n", VBPTR[i]);
        for (i = 1; VBPTR[i] != 0; i++)
        {
            cout << VBPTR[i] << " " << endl;
        }
        cout << endl;
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    主函数中的调用如下:

    void Test1()
    {
        Base b;
        Base1 b1;
        Base2 b2;
        Derive d;
        cout << "sizeof_Base = " << sizeof(Base) << endl;
        int* BvTable = (int*)(*((int*)&b));
        PrintVPTR(BvTable);
        cout << "-------------------------------" << endl;
    
        cout << "sizeof_Base1 = " << sizeof(Base1) << endl;
        int* BVPTR1 = (int*)(*((int*)&b1));                             //存放自己的虚函数(虚表)
        PrintVPTR(BVPTR1);
        int* VBPTR1 = (int*)(*((int*)&b1 + 1));//访问偏移地址以及偏移量
        PrintVBPTR(VBPTR1);
        int* VPTR1 = (int*)(*((int*)&b1 + (*(VBPTR1 + 1)) / 4 + 1));    //在Base1中访问Base虚表
        PrintVPTR(VPTR1);
        cout << "-------------------------------" << endl;
    
        cout << "sizeof_Base2 = " << sizeof(Base2) << endl;
        int* BVPTR2 = (int*)(*((int*)&b2));                      //存放自己的虚函数(虚表)
        PrintVPTR(BVPTR2);
        int* VBPTR2 = (int*)(*((int*)&b2 + 1));//访问偏移地址以及偏移量
        PrintVBPTR(VBPTR2);
        int* VPTR2 = (int*)(*((int*)&b2 + (*(VBPTR2 + 1)) / 4 + 1));//在Base2中访问Base虚表
        PrintVPTR(VPTR2);
        cout << "-------------------------------" << endl;
    
        cout << "sizeof_Derive = " << sizeof(Derive) << endl;
        int* dVPTR1 = (int*)(*((int*)&d));                           //存放自己的虚函数(虚表)
        PrintVPTR(dVPTR1);
        int* dVBPTR3 = (int*)(*((int*)&d + 1));//访问偏移地址以及偏移量
        PrintVBPTR(dVBPTR3);
        int* dVPTR2 = (int*)(*((int*)&d + 3));                       //在Derive中访问Base2虚表
        PrintVPTR(dVPTR2);
        int* dVBPTR = (int*)(*((int*)&d + 4));//访问偏移地址以及偏移量
        PrintVBPTR(dVBPTR);
        int* VPTR = (int*)(*((int*)&d + (*(dVBPTR3 + 1)) / 4 + 1));  //在Derive中访问Base虚表
        PrintVPTR(VPTR);
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41


    总结:

    1. 虚基类实例地址 = 派生类虚函数指针+派生类虚函数指针到虚基类实例地址的偏移量
    2. 可以通过虚拟继承消除二义性,但是虚拟继承的开销是增加虚函数指针。
    本文转自http://blog.csdn.net/sulijuan66/article/details/48897867
    展开全文
  • https://blog.csdn.net/j4ya_/article/details/80177897

    https://blog.csdn.net/j4ya_/article/details/80177897

    展开全文
  • 最近做题的时候经常遇到虚函数和虚继承的内存分布情况问题,自己也有点生疏了,所以,赶紧在这里回忆补充一下!先从一个类的内存分布情况开始看起:环境:VS2012class A { int a; public: A() :a(1) {} void fun...
  • 菱形继承的对象模型、继承的对象模型、以及深入探究基表的内容,为什么基表存放对象的偏移量要存放在第二个位置,而不是第一个位置?
  • 菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口。...因为成员函数不体现在类的内存大小上,所以实际上可以看到的情况是D的内存分布中含有2组A的成员变量。如下代码: class A { publ...
  • C++继承,多继承,虚继承,菱形继承的内存分布 VS开发人员命令界面查看C++类内存布局 一:无虚函数篇: 1.普通的继承(无虚函数): class A { }; class B : public A { }; B b_object; A *pA = &b_...
  • 1、什么是虚函数虚函数是一种由virtual关键字修饰的一种类内函数,可分为虚函数和纯虚函数。还是先上代码看看吧: #include<iostream> #include<memory> class A { public: virtual void func()...
  • C++虚继承虚函数

    千次阅读 2017-10-03 21:11:34
    继承在菱形继承中出现的数据二义性问题,使得数据访问时变得复杂,并且导致了数据冗存。继承则解决了从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题。关键字:virtual用法:将共同...
  • 我们可以看到,菱形继承体系中的子类在内存布局上和普通多继承体系中的子类类有很大的不一样。对于类B和C,sizeof的值变成了12,除了包含类A的成员变量dataA外还多了一个指针vbptr,类D除了继承B、C各自的成员变量...
  • 我们先看没有虚函数情况: #include <iostream> using namespace std; class A { public: void func() { cout << "A::func()" << endl; } }; class A1 :public A { public: void func() ...
  • C++菱形继承问题和继承分析

    千次阅读 2020-02-20 15:57:46
    在面向对象中,常常存在这样的事情,一个派生类它有两个或两个以上的基类,这种行为称作多重继承,示意图如下: 如果在多重继承中Class A 和Class B存在同名数据成员,则对Class C而言这个同名的数据成员容易产生二...
  • 【C++基础之二十一】菱形继承继承

    万次阅读 多人点赞 2014-01-05 09:05:41
    菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之于接口。C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Java中的接口。在多重继承中建议使用“接口”,来避免多重继承中可能出现的各种...
  • C++内存分布虚函数和虚

    千次阅读 2016-07-12 23:08:30
    虚函数:就是在类中被关键字Virtual修饰的成员函数。虚函数的作用就是实现多态,即多态性是将接口与实现进行分离,简单就是说允许将子类类型的指针赋值给父类类型的指针,那么指向基类的指针在操作它的多态类对象时...
  • 2. 虚函数表构造过程 3. 虚函数调用过程 4. 多重继承 5. 菱形继承 虚析构函数的作用及其原理 面试常见问题 内存分布 文章1(循序渐进解析) 什么是多态 概念: 定义: 同一操作作用...
  • 我们可以看到,菱形继承体系中的子类在内存布局上和普通多继承体系中的子类类有很大的不一样。对于类B和C,sizeof的值变成了12,除了包含类A的成员变量dataA外还多了一个指针vbptr,类D除了继承B、C各自的成员变量...
  • C++_多态(深入理解虚函数表

    千次阅读 多人点赞 2021-05-08 13:23:17
    时候就需要使用虚函数来构成多态。 梳理一下,多态的条件: 继承类中,需要对虚函数进行重写。 基类的指针或者引用都去调用这个虚函数 而重写的条件 3. 父子类中的函数都是虚函数。 4. 函数名参数返回值都要相
  • C++内存分布菱形继承(无虚函数)

    千次阅读 2016-07-08 19:59:55
    菱形继承的定义是:两个子类继承同一父类,而又有子类同时继承这两个子类。例如a,b两个类同时继承c,但是又有一个d类同时继承a,b类。探究的过程还是很有趣的。 菱形继承的内存布局探究花了我几天时间,探究起来还是...
  • 1.单继承的内存分布 2.多继承的内存分布 3.菱形继承的内存分布
  • 这两天在研究虚函数和虚继承时候发现很多有意思的情况,后来通过查看博客和内存分布大致总结了一下关于虚函数和虚继承间的一些东西。在写之前先记录下VS下查看内存分布的命令。通过项目-&gt;属性-&gt;C/...
  • C++虚继承内存分布

    2019-06-01 13:57:20
    本周抽空看继承的时候,研究了下继承的对象的内存分布,C++继承主要解决了菱形继承访问不明确的问题。 上述继承关系,定义DD对象的构造函数输出如下:AA BB AA CC DD,对象中存在两份AA。若AA存在成员变量,...
  • C++学习---菱形继承问题详解

    千次阅读 2019-01-19 21:33:12
    因为成员函数不体现在类的内存大小上,所以实际上可以看到的情况是D的内存分布中含有2组A的成员变量。 菱形继承存在的问题: class A { public: A():a(1){}; void printA(){cout&amp;amp;lt;&amp...
  • c++对象模型之虚基类表和虚函数表的布局(二) 注:本文的c++对象结构模型基于vs编译器的win32环境. 链接: c++对象模型之虚基类表和虚函数表的布局(一). 一、虚函数表 (4)补充 在上文中我们探究虚函数表空间布局...
  • C++继承和多态(虚函数、纯虚函数、虚继承

    千次阅读 多人点赞 2019-03-05 11:32:40
    C++继承和多态(虚函数、纯虚函数、虚继承) 一:继承 继承的概念:为了代码的复用,保留基类的原始结构,并添加派生类的新成员。 继承的本质:代码复用 我们用下图解释下: 那么我们这里就可以提出几...
  • 目录继承继承的概念继承方式基类与派生类的赋值转换作用域与隐藏派生类的默认成员函数友元与静态成员友元静态成员多继承菱形继承虚继承继承和组合什么是组合如何选择组合和继承 继承 继承的概念 继承,是面向对象三...
  • 遇到了c++钻石继承(菱形继承)的问题,看看类继承中的内存分布情况吧,有助于理解 https://www.jianshu.com/p/02183498a2c2 虚函数表 早期实验 我们来观察一下类的内存分布,大部分编译器都提供了查看C++代码中...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 617
精华内容 246
关键字:

菱形继承时候的虚函数表分布情况