类对象切割对虚函数调用的影响
来源:互联网 发布:oracle查看数据库名称 编辑:程序博客网 时间:2024/05/01 18:36
背景
现在有CFish和CAnimal两个类,并且CFish类继承于CAnimal类,它们都有breath这样的接口,只是表现形式不同,所以用虚函数来定义,类关系如下图所示;
图一 类图关系
其代码实现如下:
//基类class CAnimal{public: CAnimal() { //构造函数 cout << "CAnimal Constructor" << endl; } ~CAnimal() { //析构函数 cout << "CAnimal Destructor" << endl; } virtual void breath() { cout << "CAnimal breath" <<endl; }};//继承类CFishclass CFish:public CAnimal{public: CFish() { //构造函数 cout << "CFish Constructor" << endl; } ~CFish() { //析构函数 cout << "CFish Destructor" << endl; } virtual void breath() { cout << "CFish breath" << endl; }};
- 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
- 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
现在我们使用这两个类来分析对象切割,也就是发生Object slicing时对虚函数有何影响,示例代码如下:
int _tmain(int argc, _TCHAR* argv[]){ CFish FishObj; CFish *pFish = new CFish; cout << "case test begin..." << endl << endl; //case1 cout << "case1" <<endl; FishObj.breath(); //case2 cout << "case2" <<endl; pFish->breath(); //case3 cout << "case3" <<endl; ((CAnimal*)(&FishObj))->breath(); //case4, 对象切割,对象发生向上强制转换 cout << "case4" <<endl; ((CAnimal)FishObj).breath(); cout << "case test end..." << endl << endl; if (NULL != pFish) { delete pFish; pFish = NULL; } return 0;}
- 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
经vs2008输出如下的测试结果:
//CFish FishObj 定义对象输出CAnimal ConstructorCFish Constructor//new CFish new对象时输出CAnimal ConstructorCFish Constructorcase test begin...case1CFish breathcase2CFish breathcase3CFish breathcase4CAnimal breath //------> 出乎意外,竟不是CFish breathCAnimal Destructor //------> 出乎意外,竟有调用析构函数case test end...//函数返回栈对象析构//以及delete对象析构CFish DestructorCAnimal DestructorCFish DestructorCAnimal Destructor
- 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
- 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
毫无疑问,case1-case3都是调用CFish类中的breath函数,因为pFish 、FishObj在构造对象结束后,他们的虚函数表内容已经确定,都是存在CFish::breath接口,但case4输出结果却比较特殊,因为语句((CAnimal)FishObj)发生了向上强制转换,导致对象切割,在切割过程有对象重新构造,导致虚函数表中的内容发生变化,具体分析如下;
对象切割分析
我们知道,派生类通常会比基类大,因为派生类不仅有基类的成员还有派生类本身的成员,经过向上转换(派生类对象强制转换为基类对象),就会造成对象内容被切割(object slicing).
当代码执行到((CAnimal)FishObj).breath()语句时,发生了object slicing,其过程如下:
图二 对象切割流程
备注:本例中m_data1和m_data2是虚拟的,只有vptr是真实的。
从图中可以看出,当发生强制转换时有两个步骤:
- 发生CAnimal对象构造,同时将vptr中的值被赋值为CAnimal::breath的地址
- ((CAnimal)FishObj).breath()调用转换为临时对象的breath调用。
我们也可以从汇编代码中看出实际执行情况,汇编代码如下:
图三 代码汇编分析
在汇编代码中也验证了图二流程分析的正确性,需要注意的是在强制转换过程中,编译器会主动为我们合成一个构造函数,不是我们定义的那个构造函数,但调用的析构函数都是同一个。
通过以上分析,用例4的测试结果也就不感到意外了。
总结
如果类对象发生了切割或者向上强制转换,会产生临时对象,使得这个临时对象变成真正的CAnimal类对象,而不是CFish对象。
在分析问题过程中,也了解到一个类的构造函数有多个,但是其析构函数只有一个,因为析构函数没有返回值,没入参,也就无法实现析构函数的重载。
- 类对象切割对虚函数调用的影响
- 类对象切割对虚函数调用的影响
- 函数调用对stack的影响
- C++虚继承(七) --- 虚继承对基类构造函数调用顺序的影响
- C++ 虚继承对基类构造函数调用顺序的影响
- C++ 虚继承对基类构造函数调用顺序的影响
- C++ 虚继承对基类构造函数调用顺序的影响
- 基类对象对派生类对象的切割问题
- 虚继承和虚函数对c++对象存储模型的影响(类/对象的大小)
- ASP中函数调用对参数的影响
- __declspec(dllimport) 对【函数调用】编译结果的影响
- __declspec(dllimport) 对【函数调用】编译结果的影响
- new、delete对调用 析构函数 的影响
- ASP中函数调用对参数的影响
- 三十二、C++内存布局,对象大小计算、虚函数虚继承对类内存模型的影响
- 类中的虚函数表对sizeof的影响
- 虚函数对sizeof的影响
- 虚函数对数据结构内存的影响
- C++ class和struct的区别
- MyBatis_结合Spring_Oracle批量插入
- 在Ubuntu16-04版本上搭建离线免费地图osm(二)
- Error
- JetBrains激活
- 类对象切割对虚函数调用的影响
- View的Measure流程
- POJ 2392
- String详解, String和CharSequence区别, StringBuilder和StringBuffer的区别 (String系列之1)
- BI团队如何高效应对快速扩张的公司的需求 —— 阿里云MVP赵玮主题分享
- 微服务转载博客
- Centos6.5 安装zabbix3
- 安卓MediaCodec编码aac
- Git学习总结(17)——大型分布式团队的代码版本管理