C++对象模型
2013-09-27 22:48 by 吴秦, 24637 阅读, 37 评论, 收藏, 编辑C++对象模型
何为C++对象模型?
C++对象模型可以概括为以下2部分:
1. 语言中直接支持面向对象程序设计的部分
2. 对于各种支持的底层实现机制
语言中直接支持面向对象程序设计的部分,如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等等,这也是组里其他同学之前分享过的内容。第一部分这里我简单过一下,重点在底层实现机制。
在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在c++中,通过抽象数据类型(abstract data type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定。
概括来说,在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual。
如下面的Base类定义:
Base类定义: |
#pragma once #include<iostream> using namespace std; class Base { public: Base(int); virtual ~Base(void);
int getIBase() const; static int instanceCount(); virtual void print() const;
protected:
int iBase; static int count; }; |
Base类在机器中我们如何构建出各种成员数据和成员函数的呢?
基本C++对象模型
在介绍C++使用的对象模型之前,介绍2种对象模型:简单对象模型(a simple object model)、表格驱动对象模型(a table-driven object model)。
简单对象模型(a simple object model)
所有的成员占用相同的空间(跟成员类型无关),对象只是维护了一个包含成员指针的一个表。表中放的是成员的地址,无论上成员变量还是函数,都是这样处理。对象并没有直接保存成员而是保存了成员的指针。
表格对象模型(a table-driven object model)
这个模型在简单对象的基础上又添加了一个间接层。将成员分成函数和数据,并且用两个表格保存,然后对象只保存了两个指向表格的指针。这个模型可以保证所有的对象具有相同的大小,比如简单对象模型还与成员的个数相关。其中数据成员表中包含实际数据;函数成员表中包含的实际函数的地址(与数据成员相比,多一次寻址)。
C++对象模型
这个模型从结合上面2中模型的特点,并对内存存取和空间进行了优化。在此模型中,non static 数据成员被放置到对象内部,static数据成员, static and nonstatic 函数成员均被放到对象之外。对于虚函数的支持则分两步完成:
1. 每一个class产生一堆指向虚函数的指针,放在表格之中。这个表格称之为虚函数表(virtual table,vtbl)。
2. 每一个对象被添加了一个指针,指向相关的虚函数表vtbl。通常这个指针被称为vptr。vptr的设定(setting)和重置(resetting)都由每一个class的构造函数,析构函数和拷贝赋值运算符自动完成。
另外,虚函数表地址的前面设置了一个指向type_info的指针,RTTI(Run Time Type Identification)运行时类型识别是有编译器在编译器生成的特殊类型信息,包括对象继承关系,对象本身的描述,RTTI是为多态而生成的信息,所以只有具有虚函数的对象在会生成。
这个模型的优点在于它的空间和存取时间的效率;缺点如下:如果应用程序本身未改变,但当所使用的类的non static数据成员添加删除或修改时,需要重新编译。
模型验证测试
为了验证上述C++对象模型,我们编写如下测试代码。
模型验证测试: |
void test_base_model() { Base b1(1000); cout << "对象b1的起始内存地址:" << &b1 << endl; cout << "type_info信息:" << ((int*)*(int*)(&b1) - 1) << endl; RTTICompleteObjectLocator str= *((RTTICompleteObjectLocator*)*((int*)*(int*)(&b1) - 1)); //abstract class name from RTTI string classname(str.pTypeDescriptor->name); classname = classname.substr(4,classname.find("@@")-4); cout << classname <<endl; cout << "虚函数表地址: " << (int*)(&b1) << endl; cout << "虚函数表 — 第1个函数地址: " << (int*)*(int*)(&b1) << " 即析构函数地址:" << (int*)*((int*)*(int*)(&b1)) << endl; cout << "虚函数表 — 第2个函数地址: " << ((int*)*(int*)(&b1) + 1) << " "; typedef void(*Fun)(void); Fun pFun = (Fun)*(((int*)*(int*)(&b1)) + 1); pFun(); b1.print(); cout << endl; cout << "推测数据成员iBase地址: " << ((int*)(&b1) +1) << " 通过地址取值iBase的值:" << *((int*)(&b1) +1) << endl; cout << "Base::getIBase(): " << b1.getIBase() << endl;
b1.instanceCount(); cout << "静态函数instanceCount地址: " << b1.instanceCount << endl; } |
根据C++对象模型,实例化对象b1的起始内存地址,即虚函数表地址。
l 虚函数表的中第1个函数地址是虚析构函数地址;
l 虚函数表的中第2个函数地址是虚函数print()的地址,通过函数指针可以调用,进行验证;
l 推测数据成员iBase的地址,为虚函数表的地址 + 1,((int*)(&b1) +1);
l 静态数据成员和静态函数所在内存地址,与对象数据成员和函数成员位段不一样;
下面是测试代码输出:(从下面2个图验证了,上面的观点。)
注意:本测试代码及后面的测试代码中写的函数地址,是对应虚函数表项的地址,不是实际的函数地址。
图:测试代码输出结果
图:vs断点观察(注意看虚函数表中第一个函数的地址,名称与测试代码输出一致)
上面介绍并验证了基本的C++对象模型,引入继承之后,C++对象模型又是怎样的?
C++对象模型中加入单继承
不管是单继承、多继承,还是虚继承,如果基于“简单对象模型”,每一个基类都可以被派生类中的一个slot指出,该slot内包含基类对象的地址。这个机制的主要缺点是,因为间接性而导致空间和存取时间上的额外负担;优点则是派生类对象的大小不会因其基类的改变而受影响。
如果基于“表格驱动模型”,派生类中有一个slot指向基类表,表格中的每一个slot含一个相关的基类地址(这个很像虚函数表,内含每一个虚函数的地址)。这样每个派生类对象汗一个bptr,它会被初始化,指向其基类表。这种策略的主要缺点是由于间接性而导致的空间和存取时间上的额外负担;优点则是在每一个派生类对象中对继承都有一致的表现方式,每一个派生类对象都应该在某个固定位置上放置一个基类表指针,与基类的大小或数量无关。第二个优点是,不需要改变派生类对象本身,就可以放大,缩小、或更改基类表。
不管上述哪一种机制,“间接性”的级数都将因为集成的深度而增加。C++实际模型是,对于一般继承是扩充已有存在的虚函数表;对于虚继承添加一个虚函数表指针。
无重写的单继承
无重写,即派生类中没有于基类同名的虚函数。
Derived类: |
#pragma once #include "base.h"
class Derived : public Base { public: Derived(int); virtual ~Derived(void); virtual void derived_print(void);
protected: int iDerived; }; |
Base、Derived的类图如下所示:
Base的模型跟上面的一样,不受继承的影响。Derived不是虚继承,所以是扩充已存在的虚函数表,所以结构如下图所示:
为了验证上述C++对象模型,我们编写如下测试代码。
测试代码: |
void test_single_inherit_norewrite() { Derived d(9999); cout << "对象d的起始内存地址:" << &d << endl; cout << "type_info信息:" << ((int*)*(int*)(&d) - 1) << endl; RTTICompleteObjectLocator str= *((RTTICompleteObjectLocator*)*((int*)*(int*)(&d) - 1)); //abstract class name from RTTI string classname(str.pTypeDescriptor->name); classname = classname.substr(4,classname.find("@@")-4); cout << classname <<endl; cout << "虚函数表地址: " << (int*)(&d) << endl; cout << "虚函数表 — 第1个函数地址: " << (int*)*(int*)(&d) << " 即析构函数地址" << endl; cout << "虚函数表 — 第2个函数地址: " << ((int*)*(int*)(&d) + 1) << " "; typedef void(*Fun)(void); Fun pFun = (Fun)*(((int*)*(int*)(&d)) + 1); pFun(); d.print(); cout << endl;
cout << "虚函数表 — 第3个函数地址: " << ((int*)*(int*)(&d) + 2) << " "; pFun = (Fun)*(((int*)*(int*)(&d)) + 2); pFun(); d.derived_print(); cout << endl;
cout << "推测数据成员iBase地址: " << ((int*)(&d) +1) << " 通过地址取得的值:" << *((int*)(&d) +1) << endl; cout << "推测数据成员iDerived地址: " << ((int*)(&d) +2) << " 通过地址取得的值:" << *((int*)(&d) +2) << endl; } |
输出结果如下图所示:
有重写的单继承
派生类中重写了基类的print()函数。
Derived_Overwrite类: |
#pragma once #include "base.h" class Derived_Overrite : public Base { public: Derived_Overrite(int); virtual ~Derived_Overrite(void); virtual void print(void) const;
protected: int iDerived; }; |
Base、Derived_Overwrite的类图如下所示:
重写print()函数在虚函数表中表现如下:
为了验证上述C++对象模型,我们编写如下测试代码。
测试代码: |
void test_single_inherit_rewrite() { Derived_Overrite d(111111); cout << "对象d的起始内存地址: " << &d << endl; cout << "虚函数表地址: " << (int*)(&d) << endl; cout << "虚函数表 — 第1个函数地址: " << (int*)*(int*)(&d) << " 即析构函数地址" << endl; cout << "虚函数表 — 第2个函数地址: " << ((int*)*(int*)(&d) + 1) << " "; typedef void(*Fun)(void); Fun pFun = (Fun)*(((int*)*(int*)(&d)) + 1); pFun(); d.print(); cout << endl;
cout << "虚函数表 — 第3个函数地址: " << *((int*)*(int*)(&d) + 2) << "【结束】 "; cout << endl;
cout << "推测数据成员iBase地址: " << ((int*)(&d) +1) << " 通过地址取得的值:" << *((int*)(&d) +1) << endl; cout << "推测数据成员iDerived地址: " << ((int*)(&d) +2) << " 通过地址取得的值:" << *((int*)(&d) +2) << endl; } |
输出结果如下图所示:
特别注意下,前面的模型虚函数表中最后一项没有打印出来,本实例中共2个虚函数,打印虚函数表第3项为0。其实虚函数表以0x0000000结束,类似字符串以’ ’结束。
C++对象模型中加入多继承
从单继承可以知道,派生类中只是扩充了基类的虚函数表。如果是多继承的话,又是如何扩充的?
1) 每个基类都有自己的虚表。
2) 子类的成员函数被放到了第一个基类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个基类的虚表中的print()函数都被overwrite成了子类的print ()。这样做就是为了解决不同的基类类型的指针指向同一个子类实例,而能够调用到实际的函数。
上面3个类,Derived_Mutlip_Inherit继承自Base、Base_1两个类,Derived_Mutlip_Inherit的结构如下所示:
为了验证上述C++对象模型,我们编写如下测试代码。
测试代码: |
void test_multip_inherit() { Derived_Mutlip_Inherit dmi(3333); cout << "对象dmi的起始内存地址: " << &dmi << endl; cout << "虚函数表_vptr_Base地址: " << (int*)(&dmi) << endl; cout << "_vptr_Base — 第1个函数地址: " << (int*)*(int*)(&dmi) << " 即析构函数地址" << endl; cout << "_vptr_Base — 第2个函数地址: " << ((int*)*(int*)(&dmi) + 1) << " "; typedef void(*Fun)(void); Fun pFun = (Fun)*(((int*)*(int*)(&dmi)) + 1); pFun(); cout << endl; cout << "_vptr_Base — 第3个函数地址: " << ((int*)*(int*)(&dmi) + 2) << " "; pFun = (Fun)*(((int*)*(int*)(&dmi)) + 2); pFun(); cout << endl; cout << "_vptr_Base — 第4个函数地址: " << *((int*)*(int*)(&dmi) + 3) << "【结束】 "; cout << endl; cout << "推测数据成员iBase地址: " << ((int*)(&dmi) +1) << " 通过地址取得的值:" << *((int*)(&dmi) +1) << endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED); cout << "虚函数表_vptr_Base1地址: " << ((int*)(&dmi) +2) << endl; cout << "_vptr_Base1 — 第1个函数地址: " << (int*)*((int*)(&dmi) +2) << " 即析构函数地址" << endl; cout << "_vptr_Base1 — 第2个函数地址: " << ((int*)*((int*)(&dmi) +2) + 1) << " "; typedef void(*Fun)(void); pFun = (Fun)*((int*)*((int*)(&dmi) +2) + 1); pFun(); cout << endl; cout << "_vptr_Base1 — 第3个函数地址: " << *((int*)*(int*)((int*)(&dmi) +2) + 2) << "【结束】 "; cout << endl; cout << "推测数据成员iBase1地址: " << ((int*)(&dmi) +3) << " 通过地址取得的值:" << *((int*)(&dmi) +3) << endl; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED); cout << "推测数据成员iDerived地址: " << ((int*)(&dmi) +4) << " 通过地址取得的值:" << *((int*)(&dmi) +4) << endl; } |
输出结果如下图所示:
C++对象模型中加入虚继承
虚继承是为了解决重复继承中多个间接父类的问题的,所以不能使用上面简单的扩充并为每个虚基类提供一个虚函数指针(这样会导致重复继承的基类会有多个虚函数表)形式。
虚继承的派生类的内存结构,和普通继承完全不同。虚继承的子类,有单独的虚函数表,另外也单独保存一份父类的虚函数表,两部分之间用一个四个字节的0x00000000来作为分界。派生类的内存中,首先是自己的虚函数表,然后是派生类的数据成员,然后是0x0,之后就是基类的虚函数表,之后是基类的数据成员。
如果派生类没有自己的虚函数,那么派生类就不会有虚函数表,但是派生类数据和基类数据之间,还是需要0x0来间隔。
因此,在虚继承中,派生类和基类的数据,是完全间隔的,先存放派生类自己的虚函数表和数据,中间以0x分界,最后保存基类的虚函数和数据。如果派生类重载了父类的虚函数,那么则将派生类内存中基类虚函数表的相应函数替换。
简单虚继承(无重复继承情况)
简单虚继承的2个类Base、Derived_Virtual_Inherit1的关系如下所示:
Derived_Virtual_Inherit1的对象模型如下图:
为了验证上述C++对象模型,我们编写如下测试代码。
测试代码: |
void test_single_vitrual_inherit() { Derived_Virtual_Inherit1 dvi1(88888); cout << "对象dvi1的起始内存地址: " << &dvi1 << endl; cout << "虚函数表_vptr_Derived..地址: " << (int*)(&dvi1) << endl; cout << "_vptr_Derived — 第1个函数地址: " << (int*)*(int*)(&dvi1) << endl; typedef void(*Fun)(void); Fun pFun = (Fun)*((int*)*(int*)(&dvi1)); pFun(); cout << endl; cout << "_vptr_Derived — 第2个函数地址: " << *((int*)*(int*)(&dvi1) + 1) << "【结束】 "; cout << endl; cout << "=======================: " << ((int*)(&dvi1) +1) << " 通过地址取得的值:" << (int*)*((int*)(&dvi1) +1) << " " <<*(int*)*((int*)(&dvi1) +1) << endl; cout << "推测数据成员iDerived地址: " << ((int*)(&dvi1) +2) << " 通过地址取得的值:" << *((int*)(&dvi1) +2) << endl; cout << "=======================: " << ((int*)(&dvi1) +3) << " 通过地址取得的值:" << *((int*)(&dvi1) +3) << endl; cout << "虚函数表_vptr_Base地址: " << ((int*)(&dvi1) +4) << endl; cout << "_vptr_Base — 第1个函数地址: " << (int*)*((int*)(&dvi1) +4) << " 即析构函数地址" << endl; cout << "_vptr_Base — 第2个函数地址: " << ((int*)*((int*)(&dvi1) +4) +1) << " "; pFun = (Fun)*((int*)*((int*)(&dvi1) +4) +1); pFun(); cout << endl; cout << "_vptr_Base — 第3个函数地址: " << ((int*)*((int*)(&dvi1) +4) +2) << "【结束】 " << *((int*)*((int*)(&dvi1) +4) +2); cout << endl; cout << "推测数据成员iBase地址: " << ((int*)(&dvi1) +5) << " 通过地址取得的值:" << *((int*)(&dvi1) +5) << endl; } |
输出结果如下图所示:
菱形继承(含重复继承、多继承情况)
菱形继承关系如下图:
Derived_Virtual的对象模型如下图:
为了验证上述C++对象模型,我们编写如下测试代码。
测试代码: |
void test_multip_vitrual_inherit() { Derived_Virtual dvi1(88888); cout << "对象dvi1的起始内存地址: " << &dvi1 << endl; cout << "虚函数表_vptr_inherit1地址: " << (int*)(&dvi1) << endl; cout << "_vptr_inherit1 — 第1个函数地址: " << (int*)*(int*)(&dvi1) << endl; typedef void(*Fun)(void); Fun pFun = (Fun)*((int*)*(int*)(&dvi1)); pFun(); cout << endl; cout << "_vptr_inherit1 — 第2个函数地址: " << ((int*)*(int*)(&dvi1) + 1) << endl; pFun = (Fun)*((int*)*(int*)(&dvi1) + 1); pFun(); cout << endl; cout << "_vptr_inherit1 — 第3个函数地址: " << ((int*)*(int*)(&dvi1) + 2) << " 通过地址取得的值:" << *((int*)*(int*)(&dvi1) + 2) << "【结束】 "; cout << endl; cout << "======指向=============: " << ((int*)(&dvi1) +1) << " 通过地址取得的值:" << (int*)*((int*)(&dvi1) +1)<< " " <<*(int*)*((int*)(&dvi1) +1) << endl; cout << "推测数据成员iInherit1地址: " << ((int*)(&dvi1) +2) << " 通过地址取得的值:" << *((int*)(&dvi1) +2) << endl; // cout << "虚函数表_vptr_inherit2地址: " << ((int*)(&dvi1) +3) << endl; cout << "_vptr_inherit2 — 第1个函数地址: " << (int*)*((int*)(&dvi1) +3) << endl; pFun = (Fun)*((int*)*((int*)(&dvi1) +3)); pFun(); cout << endl; cout << "_vptr_inherit2 — 第2个函数地址: " << (int*)*((int*)(&dvi1) +3) + 1 <<" 通过地址取得的值:" << *((int*)*((int*)(&dvi1) +3) + 1) << "【结束】 " << endl; cout << endl; cout << "======指向=============: " << ((int*)(&dvi1) +4) << " 通过地址取得的值:" << (int*)*((int*)(&dvi1) +4) << " " <<*(int*)*((int*)(&dvi1) +4)<< endl; cout << "推测数据成员iInherit2地址: " << ((int*)(&dvi1) +5) << " 通过地址取得的值:" << *((int*)(&dvi1) +5) << endl; cout << "推测数据成员iDerived地址: " << ((int*)(&dvi1) +6) << " 通过地址取得的值:" << *((int*)(&dvi1) +6) << endl; cout << "=======================: " << ((int*)(&dvi1) +7) << " 通过地址取得的值:" << *((int*)(&dvi1) +7) << endl; // cout << "虚函数表_vptr_Base地址: " << ((int*)(&dvi1) +8) << endl; cout << "_vptr_Base — 第1个函数地址: " << (int*)*((int*)(&dvi1) +8) << " 即析构函数地址" << endl; cout << "_vptr_Base — 第2个函数地址: " << ((int*)*((int*)(&dvi1) +8) +1) << " "; pFun = (Fun)*((int*)*((int*)(&dvi1) +8) +1); pFun(); cout << endl; cout << "_vptr_Base — 第3个函数地址: " << ((int*)*((int*)(&dvi1) +8) +2) << "【结束】 " << *((int*)*((int*)(&dvi1) +8) +2); cout << endl; cout << "推测数据成员iBase地址: " << ((int*)(&dvi1) +9) << " 通过地址取得的值:" << *((int*)(&dvi1) +9) << endl; } |
输出结果如下图所示:
至此,C++对象模型介绍的差不多了,清楚了C++对象模型之后,很多疑问就能迎刃而解了。下面结合模型介绍一些典型问题。
如何访问成员?
前面介绍了C++对象模型,下面介绍C++对象模型的对访问成员的影响。其实清楚了C++对象模型,就清楚了成员访问机制。下面分别针对数据成员和函数成员是如何访问到的,给出一个大致介绍。
对象大小问题
其中:3个类中的函数都是虚函数
l Derived继承Base
l Derived_Virtual虚继承Base
测试对象大小: |
void test_size() { Base b; Derived d; Derived_Virtual dv; cout << "sizeof(b): " << sizeof(b) << endl; cout << "sizeof(d): " << sizeof(d) << endl; cout << "sizeof(dv): " << sizeof(dv) << endl; } |
输出如下:
因为Base中包含虚函数表指针,所有size为4;Derived继承Base,只是扩充基类的虚函数表,不会新增虚函数表指针,所以size也是4;Derived_Virtual虚继承Base,根据前面的模型知道,派生类有自己的虚函数表及指针,并且有分隔符(0x00000000),然后才是虚基类的虚函数表等信息,故大小为4+4+4=12。
空类Empty: |
#pragma once class Empty { public: Empty(void); ~Empty(void); }; |
Empty p,sizeof(p)的大小是多少?事实上并不是空的,它有一个隐晦的1byte,那是被编译器安插进去的一个char。这将使得这个class的两个对象得以在内中有独一无二的地址。
数据成员如何访问(直接取址)
跟实际对象模型相关联,根据对象起始地址+偏移量取得。
静态绑定与动态绑定
程序调用函数时,将使用那个可执行代码块呢?编译器负责回答这个问题。将源代码中的函数调用解析为执行特定的函数代码块被称为函数名绑定(binding,又称联编)。在C语言中,这非常简单,因为每个函数名都对应一个不同的额函数。在C++中,由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而编译器可以再编译过程中完成这种绑定,这称为静态绑定(static binding),又称为早期绑定(early binding)。
然而虚函数是这项工作变得更加困难。使用哪一个函数不是能在编译阶段时确定的,因为编译器不知道用户将选择哪种类型。所以,编译器必须能够在程序运行时选择正确的虚函数的代码,这被称为动态绑定(dynamic binding),又称为晚期绑定(late binding)。
使用虚函数是有代价的,在内存和执行速度方面是有一定成本的,包括:
l 每个对象都将增大,增大量为存储虚函数表指针的大小;
l 对于每个类,编译器都创建一个虚函数地址表;
l 对于每个函数调用,都需要执行一项额外的操作,即到虚函数表中查找地址。
虽然非虚函数比虚函数效率稍高,单不具备动态联编能力。
函数成员如何访问(间接取址)
跟实际对象模型相关联,普通函数(nonstatic、static)根据编译、链接的结果直接获取函数地址;如果是虚函数根据对象模型,取出对于虚函数地址,然后在虚函数表中查找函数地址。
多态如何实现?
多态的实现
多态(Polymorphisn)在C++中是通过虚函数实现的。通过前面的模型【参见“有重写的单继承”】知道,如果类中有虚函数,编译器就会自动生成一个虚函数表,对象中包含一个指向虚函数表的指针。能够实现多态的关键在于:虚函数是允许被派生类重写的,在虚函数表中,派生类函数对覆盖(override)基类函数。除此之外,还必须通过指针或引用调用方法才行,将派生类对象赋给基类对象。
上面2个类,基类Base、派生类Derived中都包含下面2个方法:
void print() const;
virtual void print_virtual() const;
这个2个方法的区别就在于一个是普通成员函数,一个是虚函数。编写测试代码如下:
测试多态代码: |
void test_polmorphisn() { Base b; Derived d;
b = d; b.print(); b.print_virtual();
Base *p; p = &d; p->print(); p->print_virtual(); } |
根据模型推测只有p->print_virtual()才实现了动态,其他3调用都是调用基类的方法。原因如下:
l b.print();b.print_virtual();不能实现多态是因为通过基类对象调用,而非指针或引用所以不能实现多态。
l p->print();不能实现多态是因为,print函数没有声明为虚函数(virtual),派生类中也定义了print函数只是隐藏了基类的print函数。
为什么析构函数设为虚函数是必要的
析构函数应当都是虚函数,除非明确该类不做基类(不被其他类继承)。基类的析构函数声明为虚函数,这样做是为了确保释放派生对象时,按照正确的顺序调用析构函数。
从前面介绍的C++对象模型可以知道,如果析构函数不定义为虚函数,那么派生类就不会重写基类的析构函数,在有多态行为的时候,派生类的析构函数不会被调用到(有内存泄漏的风险!)。
例如,通过new一个派生类对象,赋给基类指针,然后delete基类指针。
测试析构函数: |
void test_vitual_destructor() { Base *p = new Derived(); delete p; } |
如果基类的析构函数不是析构函数:
注意,缺少了派生类的析构函数调用。把析构函数声明为虚函数,调用就正常了:
相关资料
[1] 深度探索C++对象模型,侯捷
[2] 测试代码下载:https://github.com/saylorzhu/CppObjectDataModelTestCode
作者:吴秦
出处:http://www.cnblogs.com/skynet/
本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名吴秦(包含链接).
<li class="alt">
<h5>
<cite><a href="#2784164" class="layer">#6楼</a><a name="2784164" id="comment_anchor_2784164"></a>[<span class="louzhu">楼主</span>]</cite> <a id="a_comment_author_2784164" href="http://www.cnblogs.com/skynet/" target="_blank">吴秦</a> <a href="http://msg.cnblogs.com/send/%E5%90%B4%E7%A7%A6" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2013-09-28 16:41</span></small>
</h5>
<div id="comment_body_2784164" class="blog_comment_body"><a href="#2783974" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2783974);">@</a>
stallion
使用visio画的
<li class="alt">
<h5>
<cite><a href="#2784165" class="layer">#7楼</a><a name="2784165" id="comment_anchor_2784165"></a>[<span class="louzhu">楼主</span>]</cite> <a id="a_comment_author_2784165" href="http://www.cnblogs.com/skynet/" target="_blank">吴秦</a> <a href="http://msg.cnblogs.com/send/%E5%90%B4%E7%A7%A6" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2013-09-28 16:42</span></small>
</h5>
<div id="comment_body_2784165" class="blog_comment_body"><a href="#2783968" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2783968);">@</a>
春 晚
侯捷译的,ok
<li class="alt">
<h5>
<cite><a href="#2784169" class="layer">#8楼</a><a name="2784169" id="comment_anchor_2784169"></a>[<span class="louzhu">楼主</span>]</cite> <a id="a_comment_author_2784169" href="http://www.cnblogs.com/skynet/" target="_blank">吴秦</a> <a href="http://msg.cnblogs.com/send/%E5%90%B4%E7%A7%A6" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2013-09-28 16:43</span></small>
</h5>
<div id="comment_body_2784169" class="blog_comment_body"><a href="#2783996" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2783996);">@</a>
Sprout
是参考 Inside the C++ Object Model来的,不过书里面有些理论跟现在实际的实现有些差异,对一些有出入的地方进行了验证
加入了具体的单继承、多继承、虚继承等详细分析
<li class="alt">
<h5>
<cite><a href="#2786309" class="layer">#9楼</a><a name="2786309" id="comment_anchor_2786309"></a></cite> <a id="a_comment_author_2786309" href="http://www.cnblogs.com/juicy/" target="_blank">三井寿</a> <a href="http://msg.cnblogs.com/send/%E4%B8%89%E4%BA%95%E5%AF%BF" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2013-10-03 23:54</span></small>
</h5>
<div id="comment_body_2786309" class="blog_comment_body">学习了,感谢分享</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(2786309,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(2786309,'Bury',this)">反对(0)</a></div><span id="comment_2786309_avatar" style="display:none;">http://pic.cnblogs.com/face/u284648.gif</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(2786309,'Q0qryudqNSKLz3D1LRL9qcJW1AR2nggVoNAlA8Ad1AkenlFy1CY00g==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(2786309,'Q0qryudqNSKLz3D1LRL9qcJW1AR2nggVoNAlA8Ad1AkenlFy1CY00g==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#2788150" class="layer">#10楼</a><a name="2788150" id="comment_anchor_2788150"></a></cite> <a id="a_comment_author_2788150" href="http://home.cnblogs.com/u/517466/" target="_blank">不羁之风beta</a> <a href="http://msg.cnblogs.com/send/%E4%B8%8D%E7%BE%81%E4%B9%8B%E9%A3%8Ebeta" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2013-10-08 15:29</span></small>
</h5>
<div id="comment_body_2788150" class="blog_comment_body">感谢分享!十分深刻。</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(2788150,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(2788150,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(2788150,'TwGf1VQmXbD+5zQ2VSRyQcoellJeN95543R/H9olaPT3oImvIOweTA==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(2788150,'TwGf1VQmXbD+5zQ2VSRyQcoellJeN95543R/H9olaPT3oImvIOweTA==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#2911066" class="layer">#11楼</a><a name="2911066" id="comment_anchor_2911066"></a></cite> <a id="a_comment_author_2911066" href="http://www.cnblogs.com/imageoneday/" target="_blank">Ryan_Liu</a> <a href="http://msg.cnblogs.com/send/Ryan_Liu" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-04 09:07</span></small>
</h5>
<div id="comment_body_2911066" class="blog_comment_body">C++对象模型中加入单继承 倒数第三个图错了</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(2911066,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(2911066,'Bury',this)">反对(0)</a></div><span id="comment_2911066_avatar" style="display:none;">http://pic.cnblogs.com/face/475526/20130512103415.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(2911066,'NUPGza+eypfdHetJEvQTww87fgQL8vKcY+jk/Fgsq4OSlne3Cv6z0g==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(2911066,'NUPGza+eypfdHetJEvQTww87fgQL8vKcY+jk/Fgsq4OSlne3Cv6z0g==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#2911097" class="layer">#12楼</a><a name="2911097" id="comment_anchor_2911097"></a></cite> <a id="a_comment_author_2911097" href="http://www.cnblogs.com/imageoneday/" target="_blank">Ryan_Liu</a> <a href="http://msg.cnblogs.com/send/Ryan_Liu" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-04 09:34</span></small>
</h5>
<div id="comment_body_2911097" class="blog_comment_body">整天上很好,但是你的类图,也没标出哪个是虚函数啊,看着很费解<br>还有哪个xxxx => -4 是啥意思?</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(2911097,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(2911097,'Bury',this)">反对(0)</a></div><span id="comment_2911097_avatar" style="display:none;">http://pic.cnblogs.com/face/475526/20130512103415.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(2911097,'NUPGza+eypfdHetJEvQTww87fgQL8vKcY+jk/Fgsq4OSlne3Cv6z0g==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(2911097,'NUPGza+eypfdHetJEvQTww87fgQL8vKcY+jk/Fgsq4OSlne3Cv6z0g==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#2911182" class="layer">#13楼</a><a name="2911182" id="comment_anchor_2911182"></a></cite> <a id="a_comment_author_2911182" href="http://www.cnblogs.com/imageoneday/" target="_blank">Ryan_Liu</a> <a href="http://msg.cnblogs.com/send/Ryan_Liu" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-04 10:43</span></small>
</h5>
<div id="comment_body_2911182" class="blog_comment_body">如果派生类没有自己的虚函数,那么派生类就不会有虚函数表,但是派生类数据和基类数据之间,还是需要0x0来间隔。<br>这句话不对吧???</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(2911182,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(2911182,'Bury',this)">反对(0)</a></div><span id="comment_2911182_avatar" style="display:none;">http://pic.cnblogs.com/face/475526/20130512103415.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(2911182,'NUPGza+eypfdHetJEvQTww87fgQL8vKcY+jk/Fgsq4OSlne3Cv6z0g==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(2911182,'NUPGza+eypfdHetJEvQTww87fgQL8vKcY+jk/Fgsq4OSlne3Cv6z0g==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#2913012" class="layer">#14楼</a><a name="2913012" id="comment_anchor_2913012"></a>[<span class="louzhu">楼主</span>]</cite> <a id="a_comment_author_2913012" href="http://www.cnblogs.com/skynet/" target="_blank">吴秦</a> <a href="http://msg.cnblogs.com/send/%E5%90%B4%E7%A7%A6" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-08 11:54</span></small>
</h5>
<div id="comment_body_2913012" class="blog_comment_body"><a href="#2911097" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2911097);">@</a>
Ryan_Liu
画图的时候,没有注意标出哪些是虚函数
可以在虚函数表中的,或者通过源代码查看哪些是虚函数
xxxx => -4是分割符的意思,不同编译器这个分割值不太一样,vs下面是-4
<li class="alt">
<h5>
<cite><a href="#2913013" class="layer">#15楼</a><a name="2913013" id="comment_anchor_2913013"></a>[<span class="louzhu">楼主</span>]</cite> <a id="a_comment_author_2913013" href="http://www.cnblogs.com/skynet/" target="_blank">吴秦</a> <a href="http://msg.cnblogs.com/send/%E5%90%B4%E7%A7%A6" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-08 11:56</span></small>
</h5>
<div id="comment_body_2913013" class="blog_comment_body"><a href="#2911182" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2911182);">@</a>
Ryan_Liu
是对的
如果派生类没有自己的虚函数,那么派生类就不会有【自己的】虚函数表。
0x0也是存在的
<li class="alt">
<h5>
<cite><a href="#2913016" class="layer">#16楼</a><a name="2913016" id="comment_anchor_2913016"></a>[<span class="louzhu">楼主</span>]</cite> <a id="a_comment_author_2913016" href="http://www.cnblogs.com/skynet/" target="_blank">吴秦</a> <a href="http://msg.cnblogs.com/send/%E5%90%B4%E7%A7%A6" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-08 11:58</span></small>
</h5>
<div id="comment_body_2913016" class="blog_comment_body"><a href="#2911097" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2911097);">@</a>
Ryan_Liu
哪个地方,没找到
指下具体哪个地方错了☺
<li class="alt">
<h5>
<cite><a href="#2917249" class="layer">#17楼</a><a name="2917249" id="comment_anchor_2917249"></a></cite> <a id="a_comment_author_2917249" href="http://www.cnblogs.com/frozenxia/" target="_blank">Frozenxia</a> <a href="http://msg.cnblogs.com/send/Frozenxia" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-14 11:49</span></small>
</h5>
<div id="comment_body_2917249" class="blog_comment_body">您好,在单继承模型中,“重写print()函数在虚函数表中表现如下:” 这句话下面的那个图里面,虚函数表第一个是析构函数Derived::~Derived(); 那基类Base的析构函数放在那个地方呢,在图里面没有看到有地方表示基类的~Base()呢。 谢谢啦</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(2917249,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(2917249,'Bury',this)">反对(0)</a></div><span id="comment_2917249_avatar" style="display:none;">http://pic.cnblogs.com/face/u379504.jpg?id=01225756</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(2917249,'oWj4baMQI35D7GctnpDSJbPnHjyDzgw9psux55vGHdYJoCxxgKNinQ==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(2917249,'oWj4baMQI35D7GctnpDSJbPnHjyDzgw9psux55vGHdYJoCxxgKNinQ==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#2924000" class="layer">#18楼</a><a name="2924000" id="comment_anchor_2924000"></a></cite> <a id="a_comment_author_2924000" href="http://www.cnblogs.com/lovesaber/" target="_blank">steel_heart</a> <a href="http://msg.cnblogs.com/send/steel_heart" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-23 18:06</span></small>
</h5>
<div id="comment_body_2924000" class="blog_comment_body">若问楼主的排版和代码高亮是怎么弄的?<br>博客园自带的代码高亮能在代码上加标题么?<br><div class="cnblogs_Highlighter sh-gutter"><div><div id="highlighter_439332" class="syntaxhighlighter cpp"><div class="toolbar"><span><a href="#" class="toolbar_item command_help help">?</a></span></div><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="gutter"><div class="line number1 index0 alt2">1</div></td><td class="code"><div class="container"><div class="line number1 index0 alt2"><code class="cpp preprocessor">#include<iostream></code></div></div></td></tr></tbody></table></div></div></div></div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(2924000,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(2924000,'Bury',this)">反对(0)</a></div><span id="comment_2924000_avatar" style="display:none;">http://pic.cnblogs.com/face/u169101.jpg?id=21160713</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(2924000,'fU8YFur3n73Jy3m0h0/h17NJDaOK/TwuPVp8BXYORTHN+N3m+jlY4Q==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(2924000,'fU8YFur3n73Jy3m0h0/h17NJDaOK/TwuPVp8BXYORTHN+N3m+jlY4Q==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#2924901" class="layer">#19楼</a><a name="2924901" id="comment_anchor_2924901"></a>[<span class="louzhu">楼主</span>]</cite> <a id="a_comment_author_2924901" href="http://www.cnblogs.com/skynet/" target="_blank">吴秦</a> <a href="http://msg.cnblogs.com/send/%E5%90%B4%E7%A7%A6" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-04-24 22:29</span></small>
</h5>
<div id="comment_body_2924901" class="blog_comment_body"><a href="#2924000" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2924000);">@</a>
BillyForever
排版用的word
代码高亮,从vs复制出来代码粘贴到word会保留高亮
<li class="alt">
<h5>
<cite><a href="#3019971" class="layer">#20楼</a><a name="3019971" id="comment_anchor_3019971"></a></cite> <a id="a_comment_author_3019971" href="http://www.cnblogs.com/frozenxia/" target="_blank">Frozenxia</a> <a href="http://msg.cnblogs.com/send/Frozenxia" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-08-31 00:10</span></small>
</h5>
<div id="comment_body_3019971" class="blog_comment_body">你好,在简单虚继承的例子里面,如果子类没有虚函数。第一个地址的值我测试的结果是*((int*)*((int*)&dr))=0,而不是-4,第二个地址的值*((int*)&dr+1)是iderive的值,第三个值就是0.不知道作何解释?<br>我的子类定义:<br>class Derive:virtual public Base<br>{<br>public:<br> int iderived_virtual;<br> Derive(int id):iderived_virtual(id),Base(1,3){}<br> ~Derive(){}<br> void print(){cout << "iderive print" << endl;}<br> //virtual void print_inherit(){cout << "inherient print" << endl;}<br>};</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3019971,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3019971,'Bury',this)">反对(0)</a></div><span id="comment_3019971_avatar" style="display:none;">http://pic.cnblogs.com/face/u379504.jpg?id=01225756</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3019971,'oWj4baMQI35D7GctnpDSJbPnHjyDzgw9psux55vGHdYJoCxxgKNinQ==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3019971,'oWj4baMQI35D7GctnpDSJbPnHjyDzgw9psux55vGHdYJoCxxgKNinQ==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3083263" class="layer">#21楼</a><a name="3083263" id="comment_anchor_3083263"></a></cite> <a id="a_comment_author_3083263" href="http://www.cnblogs.com/zhchngzng/" target="_blank">chng</a> <a href="http://msg.cnblogs.com/send/chng" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2014-12-10 23:08</span></small>
</h5>
<div id="comment_body_3083263" class="blog_comment_body">对VS的C++对象分析地好详尽!的确书里说的一些东西,随着编译器演化显得有些过时了。而且,gnu和VS区别还是蛮大的,比如<br> class A<br> {<br> virtual void fb(){}<br> };<br> class B: public A<br> {<br> };<br> class Bv: public virtual A<br> {<br> };<br>这时候gnu的处理是:三个类的size都是4,而且没有分隔符。<br><br>再比如,书上说&class::member可以得到member的相对offset,但是程序写出来发现&class::mem1和&class::mem2都是1,不知您知不知道现在的VS有没有这么一个offset了?<br><br>最近有一点不明的是,前面说vptr地址的前一位是type_info,在第四章里又说,对于虚继承,vptr地址的前一位是虚基类相对vptr所有者的起始地址的offset,而程序打出来却没有所谓的offset,请问您知道虚基类里面offset到底在哪么?<br><br>还有还有,第四章最后做了大量的性能对比试验,开CC的优化(应该是-O)后性能提升特别多,您了解-O都做了哪些程序层面的优化么?求教!</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3083263,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3083263,'Bury',this)">反对(0)</a></div><span id="comment_3083263_avatar" style="display:none;">http://pic.cnblogs.com/face/194467/20140310120558.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3083263,'7b5+MSfumieSNLup4UeZSSO5WNK6rKtq4R3OEoR4VKGjp33Ru8wNZw==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3083263,'7b5+MSfumieSNLup4UeZSSO5WNK6rKtq4R3OEoR4VKGjp33Ru8wNZw==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3178309" class="layer">#22楼</a><a name="3178309" id="comment_anchor_3178309"></a></cite> <a id="a_comment_author_3178309" href="http://home.cnblogs.com/u/674595/" target="_blank">sherlock-coding</a> <a href="http://msg.cnblogs.com/send/sherlock-coding" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-05-07 19:33</span></small>
</h5>
<div id="comment_body_3178309" class="blog_comment_body">楼主,请问一下,模型验证测试中,<br>Fun pFun = (Fun)*(((int*)*(int*)(&b1)) + 1);<br>pFun();<br>b1.print();<br>这两种调用为什么结果不同?</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3178309,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3178309,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3178309,'H52Z82xEYDnPbSvPmRhZ6gUGjO6k3n+bHpl4+YBPAdVN4AQAH2W2tA==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3178309,'H52Z82xEYDnPbSvPmRhZ6gUGjO6k3n+bHpl4+YBPAdVN4AQAH2W2tA==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3181705" class="layer">#23楼</a><a name="3181705" id="comment_anchor_3181705"></a></cite> <a id="a_comment_author_3181705" href="http://home.cnblogs.com/u/754340/" target="_blank">sw55555</a> <a href="http://msg.cnblogs.com/send/sw55555" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-05-12 12:54</span></small>
</h5>
<div id="comment_body_3181705" class="blog_comment_body"><a href="#3178309" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,3178309);">@</a>
sherlock-coding
第一种是把成员函数当作一个C函数来调用的,没有使用对象,所以对象指针因为随机值
第二种是才是正确的成员调用函数方法
主要还是看print函数的代码有没有访问对象的数据成员,没有访问其实两种调用产生的结果都是一样的
<li class="alt">
<h5>
<cite><a href="#3212900" class="layer">#24楼</a><a name="3212900" id="comment_anchor_3212900"></a></cite> <a id="a_comment_author_3212900" href="http://www.cnblogs.com/reasno/" target="_blank">reasno</a> <a href="http://msg.cnblogs.com/send/reasno" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-06-22 12:39</span></small>
</h5>
<div id="comment_body_3212900" class="blog_comment_body"><a href="#2784169" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,2784169);">@</a>
吴秦
对,里面的内容有些地方确实令人疑惑,不过你这个写得很清楚,必须赞!
<li class="alt">
<h5>
<cite><a href="#3220696" class="layer">#25楼</a><a name="3220696" id="comment_anchor_3220696"></a></cite> <a id="a_comment_author_3220696" href="http://home.cnblogs.com/u/708070/" target="_blank">会说话的猫</a> <a href="http://msg.cnblogs.com/send/%E4%BC%9A%E8%AF%B4%E8%AF%9D%E7%9A%84%E7%8C%AB" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-07-03 10:40</span></small>
</h5>
<div id="comment_body_3220696" class="blog_comment_body">为什么虚继承的派生类数据部分会安插一个Xxxx指针进行,并且指向的是-4呢?</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3220696,'Digg',this)">支持(1)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3220696,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3220696,'jfXwizmCzfSh87ya5S6CBTXo0vQGTmxHXTadFoU/k65M7grq9COgfA==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3220696,'jfXwizmCzfSh87ya5S6CBTXo0vQGTmxHXTadFoU/k65M7grq9COgfA==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3256344" class="layer">#26楼</a><a name="3256344" id="comment_anchor_3256344"></a></cite> <a id="a_comment_author_3256344" href="http://www.cnblogs.com/sdlwlxf/" target="_blank">sdlwlxf</a> <a href="http://msg.cnblogs.com/send/sdlwlxf" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-08-28 17:23</span></small>
</h5>
<div id="comment_body_3256344" class="blog_comment_body">谁能告诉我type_info是怎么确定的吗?为什么有的是Base,有的是Derive</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3256344,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3256344,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3256344,'suyuT++gBd76ZKak/nATXNNr68hEtzZbPjuJ7Gaqnnd08ApWZHVpxA==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3256344,'suyuT++gBd76ZKak/nATXNNr68hEtzZbPjuJ7Gaqnnd08ApWZHVpxA==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3275464" class="layer">#27楼</a><a name="3275464" id="comment_anchor_3275464"></a></cite> <a id="a_comment_author_3275464" href="http://www.cnblogs.com/icooper/" target="_blank">黑桃七</a> <a href="http://msg.cnblogs.com/send/%E9%BB%91%E6%A1%83%E4%B8%83" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-09-27 20:35</span></small>
</h5>
<div id="comment_body_3275464" class="blog_comment_body">不错,收藏了。</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3275464,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3275464,'Bury',this)">反对(0)</a></div><span id="comment_3275464_avatar" style="display:none;">http://pic.cnblogs.com/face/707943/20151011173503.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3275464,'Yj9aAZywRYvpUexjvonHtzs7Rk9RngWn8ZACherDtX20h4FgqiNJ3g==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3275464,'Yj9aAZywRYvpUexjvonHtzs7Rk9RngWn8ZACherDtX20h4FgqiNJ3g==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3278735" class="layer">#28楼</a><a name="3278735" id="comment_anchor_3278735"></a></cite> <a id="a_comment_author_3278735" href="http://home.cnblogs.com/u/818715/" target="_blank">lmxk</a> <a href="http://msg.cnblogs.com/send/lmxk" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-10-05 14:54</span></small>
</h5>
<div id="comment_body_3278735" class="blog_comment_body">描述得不太清楚,尤其后面得到虚继承,依旧感谢</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3278735,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3278735,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3278735,'fE+zcSkZy8noKPUcKr+vobp3JM4cWpOc0H6M+Ao0UwFI7Bil6+1B3g==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3278735,'fE+zcSkZy8noKPUcKr+vobp3JM4cWpOc0H6M+Ao0UwFI7Bil6+1B3g==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3289244" class="layer">#29楼</a><a name="3289244" id="comment_anchor_3289244"></a></cite> <a id="a_comment_author_3289244" href="http://www.cnblogs.com/QG-whz/" target="_blank">melonstreet</a> <a href="http://msg.cnblogs.com/send/melonstreet" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-10-21 15:59</span></small>
</h5>
<div id="comment_body_3289244" class="blog_comment_body">感谢博主的文章,引发了很多思考,以前没注意的问题都重新捋了一遍。<br>有个问题:<br>在“无重写的单继承”那部分内容中,子类 Derived声明了自己新的一个虚函数derived_print,然而Derived对象却没生成一个vptr,且Derived对象中的Base部分中的vptr也没有指向虚函数derived_print的指针,这是为什么呢?(在VS2013中测试).这和博主说的“扩充已存在的虚函数表”并不符合...</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3289244,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3289244,'Bury',this)">反对(0)</a></div><span id="comment_3289244_avatar" style="display:none;">http://pic.cnblogs.com/face/610439/20160507224401.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3289244,'oiBD/SPU3b7cfUH69wFhKQLGyRdpgoHQIZnZKeTdMRspQZdKUHk/ew==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3289244,'oiBD/SPU3b7cfUH69wFhKQLGyRdpgoHQIZnZKeTdMRspQZdKUHk/ew==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3298684" class="layer">#30楼</a><a name="3298684" id="comment_anchor_3298684"></a></cite> <a id="a_comment_author_3298684" href="http://home.cnblogs.com/u/831978/" target="_blank">小生不懂</a> <a href="http://msg.cnblogs.com/send/%E5%B0%8F%E7%94%9F%E4%B8%8D%E6%87%82" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-11-04 11:22</span></small>
</h5>
<div id="comment_body_3298684" class="blog_comment_body">楼主这篇文章写的非常详细;<br>我自己也在linux_64 G++的环境下 分析 了下:<br>C++对象模型中加入虚继承 这一章节可能和vs生成的不一样<br>g++ dump 出来class layout 如下:<br>Vtable for Derived_Virtual_Inherit1<br>Derived_Virtual_Inherit1::_ZTV24Derived_Virtual_Inherit1: 14u entries<br>0 16u<br>8 (int (*)(...))0<br>16 (int (*)(...))(& _ZTI24Derived_Virtual_Inherit1)<br>24 Derived_Virtual_Inherit1::~Derived_Virtual_Inherit1<br>32 Derived_Virtual_Inherit1::~Derived_Virtual_Inherit1<br>40 Derived_Virtual_Inherit1::print<br>48 Derived_Virtual_Inherit1::inherit1_print<br>56 -16u<br>64 -16u<br>72 (int (*)(...))-0x00000000000000010<br>80 (int (*)(...))(& _ZTI24Derived_Virtual_Inherit1)<br>88 Derived_Virtual_Inherit1::_ZTv0_n24_N24Derived_Virtual_Inherit1D1Ev<br>96 Derived_Virtual_Inherit1::_ZTv0_n24_N24Derived_Virtual_Inherit1D0Ev<br>104 Derived_Virtual_Inherit1::_ZTv0_n32_NK24Derived_Virtual_Inherit15printEv<br><br>VTT for Derived_Virtual_Inherit1<br>Derived_Virtual_Inherit1::_ZTT24Derived_Virtual_Inherit1: 2u entries<br>0 ((& Derived_Virtual_Inherit1::_ZTV24Derived_Virtual_Inherit1) + 24u)<br>8 ((& Derived_Virtual_Inherit1::_ZTV24Derived_Virtual_Inherit1) + 88u)<br><br>Class Derived_Virtual_Inherit1<br> size=32 align=8<br> base size=12 base align=8<br>Derived_Virtual_Inherit1 (0x7f5592c77cb0) 0<br> vptridx=0u vptr=((& Derived_Virtual_Inherit1::_ZTV24Derived_Virtual_Inherit1) + 24u)<br> Base (0x7f5592c77d20) 16 virtual<br> vptridx=8u vbaseoffset=-0x00000000000000018 vptr=((& Derived_Virtual_Inherit1::_ZTV24Derived_Virtual_Inherit1) + 88u)<br><br>从40和 48来看 这两个virtual funcs 并没有分别生成在derived base 的vtalbe中 而是都生成在了derved中。base:vtable中生成了一个print 的thunk.<br><br>而且 testcpp 中typedef void(*Fun)(void ); pFun();这样的Fun调用是用问题的。因为pFun 这样调是没有传入this指针 所以里面打应的值是个不正常的数-236..... 我这样改了下<br> typedef void(*Fun)(void *);<br> pFun(&dvi1); 里面是可以正确打出 888888</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3298684,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3298684,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3298684,'8jSevgFCop+v+laBX2ey+dTrC+bIOR6OINB+P/t7WvU7UJtnCBZ0gw==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3298684,'8jSevgFCop+v+laBX2ey+dTrC+bIOR6OINB+P/t7WvU7UJtnCBZ0gw==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3298687" class="layer">#31楼</a><a name="3298687" id="comment_anchor_3298687"></a></cite> <a id="a_comment_author_3298687" href="http://home.cnblogs.com/u/831978/" target="_blank">小生不懂</a> <a href="http://msg.cnblogs.com/send/%E5%B0%8F%E7%94%9F%E4%B8%8D%E6%87%82" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2015-11-04 11:24</span></small>
</h5>
<div id="comment_body_3298687" class="blog_comment_body"><a href="#3178309" title="查看所回复的评论" onclick="commentManager.renderComments(0,50,3178309);">@</a>
sherlock-coding
可试下typedef void(*Fun)(void *);
pFun(&dvi1);
<li class="alt">
<h5>
<cite><a href="#3414319" class="layer">#32楼</a><a name="3414319" id="comment_anchor_3414319"></a></cite> <a id="a_comment_author_3414319" href="http://www.cnblogs.com/tgycoder/" target="_blank">RunningSnail</a> <a href="http://msg.cnblogs.com/send/RunningSnail" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2016-04-21 17:36</span></small>
</h5>
<div id="comment_body_3414319" class="blog_comment_body">敬佩之至!</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3414319,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3414319,'Bury',this)">反对(0)</a></div><span id="comment_3414319_avatar" style="display:none;">http://pic.cnblogs.com/face/702782/20150108201714.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3414319,'vUMi1QtzNhwyqfnB5UVb6/mBpnxC33NyVWavc3BheRmP6JeSbDujpQ==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3414319,'vUMi1QtzNhwyqfnB5UVb6/mBpnxC33NyVWavc3BheRmP6JeSbDujpQ==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3439197" class="layer">#33楼</a><a name="3439197" id="comment_anchor_3439197"></a></cite> <a id="a_comment_author_3439197" href="http://www.cnblogs.com/xiaohaigege/" target="_blank">梦幻之海</a> <a href="http://msg.cnblogs.com/send/%E6%A2%A6%E5%B9%BB%E4%B9%8B%E6%B5%B7" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2016-05-25 20:26</span></small>
</h5>
<div id="comment_body_3439197" class="blog_comment_body">写的真好,收获很多,比看那个蛋疼的书好多了。怒赞</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3439197,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3439197,'Bury',this)">反对(0)</a></div><span id="comment_3439197_avatar" style="display:none;">http://pic.cnblogs.com/face/802629/20160124094444.png</span>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3439197,'jQTfyYMU/mBAi3d4zc3ovGhLYuO3w/npsxIDHWIjW2gIgqAP+mwo1A==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3439197,'jQTfyYMU/mBAi3d4zc3ovGhLYuO3w/npsxIDHWIjW2gIgqAP+mwo1A==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3458507" class="layer">#34楼</a><a name="3458507" id="comment_anchor_3458507"></a></cite> <a id="a_comment_author_3458507" href="http://home.cnblogs.com/u/809752/" target="_blank">不知者无罪</a> <a href="http://msg.cnblogs.com/send/%E4%B8%8D%E7%9F%A5%E8%80%85%E6%97%A0%E7%BD%AA" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2016-06-25 10:16</span></small>
</h5>
<div id="comment_body_3458507" class="blog_comment_body">class X{ <br> int i;<br>public:<br> X(int k=0) :i(k){}//constructor<br>virtual void foo(){} <br>};<br>class Y : virtual public X{<br> int h; <br> Y(int k=0) :X(),h(k){}//constructor<br> virtual void foo(){}<br> virtual void funY(){}<br>};<br><br>我想请教一个问题,就是在32为机器上,为什么去掉两个构造函数,大小就变为20,而如果带有两个构造函数大小就是24呢?如上所示。</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3458507,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3458507,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3458507,'WylbsdWJeyOQfuEttFqrx8AXdAj9mC5Q6QIAgPB0Hlf6AHqLhybV2g==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3458507,'WylbsdWJeyOQfuEttFqrx8AXdAj9mC5Q6QIAgPB0Hlf6AHqLhybV2g==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3471916" class="layer">#35楼</a><a name="3471916" id="comment_anchor_3471916"></a></cite> <a id="a_comment_author_3471916" href="http://www.cnblogs.com/TangBiao/" target="_blank">小默T</a> <a href="http://msg.cnblogs.com/send/%E5%B0%8F%E9%BB%98T" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2016-07-18 14:03</span></small>
</h5>
<div id="comment_body_3471916" class="blog_comment_body">写的真好,收获很多,比看那个蛋疼的书好多了。怒赞</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3471916,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3471916,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3471916,'huRJIra0KpX21v9hp+sNmC0kFgMYRU1y2hQvjEl3s1TKaCf5uVN6hA==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3471916,'huRJIra0KpX21v9hp+sNmC0kFgMYRU1y2hQvjEl3s1TKaCf5uVN6hA==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3486765" class="layer">#36楼</a><a name="3486765" id="comment_anchor_3486765"></a></cite> <a id="a_comment_author_3486765" href="http://home.cnblogs.com/u/1006765/" target="_blank">fatal_virus</a> <a href="http://msg.cnblogs.com/send/fatal_virus" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2016-08-10 14:35</span></small>
</h5>
<div id="comment_body_3486765" class="blog_comment_body">大家好,有没有人尝试输出“C++对象模型中加入多继承”中第一个实验中两个被继承的基类的_vptr指向的type_info查看类的名字,我做的实验是Base和Base_1的看到的名字都是Derived_Mutlip_Inherit而不是作者图上说的一个Base一个Base_1,请问如何理解,谢谢!</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3486765,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3486765,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3486765,'ZYZ9HD40QaALV2f4YtXoBXV79EpxEpcWA0FNKKWktpCGasMoBhPKTA==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3486765,'ZYZ9HD40QaALV2f4YtXoBXV79EpxEpcWA0FNKKWktpCGasMoBhPKTA==')">引用</a></span></div>
</li>
<li class="alt">
<h5>
<cite><a href="#3767776" class="layer">#37楼</a><a name="3767776" id="comment_anchor_3767776"></a><span id="comment-maxId" style="display:none;">3767776</span><span id="comment-maxDate" style="display:none;">2017/8/27 21:42:15</span></cite> <a id="a_comment_author_3767776" href="http://home.cnblogs.com/u/1207830/" target="_blank">hqwu</a> <a href="http://msg.cnblogs.com/send/hqwu" title="发送站内短消息" class="sendMsg2This"> </a><small> <span class="comment_date">2017-08-27 21:42</span></small>
</h5>
<div id="comment_body_3767776" class="blog_comment_body">vs2013下:<br><br><br>如果派生类没有自己的虚函数,那么派生类就不会有虚函数表,但是派生类数据和基类数据之间,还是需要0x0来间隔。<br><br>这句话是错的。内存分布是紧挨着的,没有分界0x0这一说。<br>派生类虚继承基类的时候,总会有一个指向虚函数表的vptr,而后是派生类的变量,然后再是基类。<br>虚继承中,即使派生类和基类都没有虚函数,派生类也会有vptr的,其指向的内存地址所存储的是0x00罢了。。。<br>例如:<br>class J<br>{<br>public:<br> J(int a = 1) :j(a){}<br> void k(){<br> cout << "no" << endl;<br> }<br> int j;<br>};<br>class I:virtual public J<br>{<br>public:<br> I(int a = 2) :J(),i(a){}<br> void k(){<br> cout << "no" << endl;<br> }<br> int i;<br>};<br><br>测试发现<br>类I对象的大小为12.<br>在vs2013下做的测试。。</div><div class="comment_vote"><a href="javascript:void(0);" class="comment_digg" onclick="return voteComment(3767776,'Digg',this)">支持(0)</a><a href="javascript:void(0);" class="comment_bury" onclick="return voteComment(3767776,'Bury',this)">反对(0)</a></div>
<div class="opt_comment"><span class="comment_actions"><a href="javascript:void(0);" onclick="return ReplyComment(3767776,'JH9gXuKZtj1RxBYO20gFF2tiMWr2HHFWFVLCtUZWUqI8fcevEr+CcQ==')">回复</a><a href="javascript:void(0);" onclick="return QuoteComment(3767776,'JH9gXuKZtj1RxBYO20gFF2tiMWr2HHFWFVLCtUZWUqI8fcevEr+CcQ==')">引用</a></span></div>
</li>
</div>
yhimage
绝对原创
是为组里内部分析准备的