C++对象布局及多态探索之菱形结构虚继承

这次我们看看菱形结构的虚继承。虚继承的引入本就是为了解决复杂结构的继承体系问题。上一篇我们在讨论虚继承时用的是一个简单的继承结构,只是为了打个铺垫。

我们先看看这几个类,这是一个典型的菱形继承结构。C100和C101通过虚继承共享同一个父类C041。C110则从C100和C101多重继承而来。

struct C041
{
 C041() : c_(0x01) {}
 virtual void foo() { c_ = 0x02; }
 char c_;
};
struct C100 : public virtual C041
{
 C100() : c_(0x02) {}
 char c_;
};
struct C101 : public virtual C041
{
 C101() : c_(0x03) {}
 char c_;
};
struct C110 : public C100, public C101
{
 C110() : c_(0x04) {}
 char c_;
};


运行如下代码:

PRINT_SIZE_DETAIL(C110)

结果为:

The size of C110 is 16
The detail of C110 is 28 c3 45 00 02 1c c3 45 00 03 04 18 c3 45 00 01


我们可以象上一篇一样,画出对象的内存布局。

|C100,5 |C101,5 |C110,1 |C041,5 |
|ospt,4,11 |m,1 |ospt,4,6 |m,1 |m,1 |vtpt,4 |m1 |


(注:为了不折行,我用了缩写。ospt代表偏移值指针、m代表成员变量、vtpt代表虚表指针。第一个数字是该区域的大小,即字节数。只有偏移值指针有第二个数字,第二个数字就是偏移值指针指向的偏移值的大小。)

可以看到对象的内存布局中只有一个C041,即祖父类的部分只有一份,且放在最后面。这就是菱形继承。对比前面几篇的讨论,我们可以知道,如果没有用虚继承机制,那么在C041对象的内存布局中会出现两份C041部分,这也就是所谓的V型继承。相应的对象布局为:C041+C100+C041+C101 +C110。在V型继承中是不能直接从C110,即孙子类,直接转型到C041,即祖父类的。因为在对象的布局中有两份祖父类的实体,一份从C100而来,一份从C101而来。编译器在决议时会存在二义性,它不知道转型后到底用哪一份实体。虽然可以通过先转型到某一父类,然后再转型到祖父类来解决。但使用这种方法时,如果改写了祖父类的成员变量的内容,runtime是不会同步两个祖父类实体的状态,因此可能会有语义错误。

时间: 2024-10-20 08:52:22

C++对象布局及多态探索之菱形结构虚继承的相关文章

C++对象布局及多态实现探索之虚函数调用

我们再看看虚成员函数的调用.类C041中含有虚成员函数,它的定义如下: struct C041{C041() : c_(0x01) {}virtual void foo() { c_ = 0x02; }char c_;}; 执行如下代码: C041 obj;PRINT_DETAIL(C041, obj)PRINT_VTABLE_ITEM(obj, 0, 0)obj.foo();C041 * pt = &obj;pt->foo(); 结果如下: The detail of C041 is 14

C++对象布局及多态实现的探索

前言 本文通过观察对象的内存布局,跟踪函数调用的汇编代码.分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等. 写这篇文章源于我在论坛上看到的一个贴子.有人问VC使用了哪种方式来实现虚继承.当时我写了一点代码想验证一下,结果发现情况比我想象的要复杂.所以我就干脆认真把相关的问题都过了一遍,并记录成本文. 我对于C++对象模型的知识主要来自于Lippman的书<Inside the C++ Object Model>,中译版为候捷翻的<深度探索C++对象模型>,中英版

C++对象布局及多态实现探索之虚继承

下面我们来看虚继承.首先看看这C020类,它从C010虚继承:} struct C010{ C010() : c_(0x01) {} void foo() { c_ = 0x02; } char c_;};struct C020 : public virtual C010{ C020() : c_(0x02) {} char c_;}; 运行如下代码,查看对象的内存布局: PRINT_SIZE_DETAIL(C020) 结果为: The size of C020 is 6The detail o

C++对象布局及多态实现的探索(二)

虚函数的类的对象布局(1) 如果类中存在虚函数时,情况会怎样呢?我们知道当一个类中有虚函数时,编译器会为该类产生一个虚函数表,并在它的每一个对象中插入一个指向该虚函数表的指针,通常这个指针是插在对象的起始位置.所谓的虚函数表实际就是一个指针数组,其中的指针指向真正的函数起始地址.我们来验证一下,定义一个无成员变量的类C040,内含一个虚函数. struct C040 { virtual void foo() {} }; 运行如下代码打印它的大小及对象中的内容. PRINT_SIZE_DETAIL

C++对象布局及多态实现之成员函数的调用

从这部分开始我们除了利用内存的信息打印来进行探索外,更多的会通过跟踪和观察编译器产生的汇编代码来理解编译器对这些语言特性的实现方式.汇编方面知识的讨论超出了本文的范围,我只对和我们讨论相关的汇编代码进行解析.理解本文要讨论的知识并不需要有很完整的汇编知识,但必须了解起码的概念. 下面我们看看引入虚继承后的影响.为了有所对比我们首先看看普通成员函数的调用情况. 执行如下代码,它包括了对象的普通成员函数调用,类的静态成员函数调用.通过指针调用普通成员函数: C010 obj;PRINT_OBJ_AD

C++对象布局及多态实现探索之内存布局

为了便于分析和观察对象的内存布局,我把代码生成时的结构成员对齐选项设置为1字节,默认为8字节.如果你在自己的工程下编译文中的代码,请做同样的设置.因为我写了一些函数打印对象中的布局信息,如果对象选项不是1字节,运行这些代码会出现指针异常错误. 普通类对象的内存布局 首先我们从普通类对象的内存布局开始.c000为一个空类,定义如下: struct c000 {}; 运行如下代码打印它的大小及对象中的内容. print_size_detail(c000) 结果为: the size of c000

C++对象布局及多态之虚成员函数调用

在构造函数中调用虚成员函数,虽然这是个不很常用的技术,但研究一下可以加深对虚函数机制及对象构造过程的理解.这个问题也和一般直观上的认识有所差异.先看看下面的两个类定义. struct C180 { C180() { foo(); this->foo(); } virtual foo() { cout << "<< C180.foo this: " << this << " vtadr: " << *(

C++对象布局及多态实现之动态和强制转换

为了验证前面提到过的类型动态转换(即dynamic_cast转换),以及对象类型的强制转换.我们利用前面定义的C041.C042及C082类来进行验证. 运行下列代码: c082.C041::c_ = 0x05;PRINT_VTABLE_ITEM(c041, 0, 0)PRINT_DETAIL(C041, ((C041)c082))PRINT_VTABLE_ITEM(((C041)c082), 0, 0)PRINT_VTABLE_ITEM(c082, 5, 0)C042 * pt = dynam

C++对象布局及多态实现之带虚函数的类

如果类中存在虚函数时,情况会怎样呢?我们知道当一个类中有虚函数时,编译器会为该类产生一个虚函数表,并在它的每一个对象中插入一个指向该虚函数表的指针,通常这个指针是插在对象的起始位置.所谓的虚函数表实际就是一个指针数组,其中的指针指向真正的函数起始地址.我们来验证一下,定义一个无成员变量的类C040,内含一个虚函数. struct C040{ virtual void foo() {}}; 运行如下代码打印它的大小及对象中的内容. PRINT_SIZE_DETAIL(C040) 结果为: The