类对象切割对虚函数调用的影响

来源:互联网 发布:php新闻发布系统 编辑:程序博客网 时间:2024/05/01 14:04

背景

现在有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;    }};

现在我们使用这两个类来分析对象切割,也就是发生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;}

经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

毫无疑问,case1-case3都是调用CFish类中的breath函数,因为pFish 、FishObj在构造对象结束后,他们的虚函数表内容已经确定,都是存在CFish::breath接口,但case4输出结果却比较特殊,因为语句((CAnimal)FishObj)发生了向上强制转换,导致对象切割,在切割过程有对象重新构造,导致虚函数表中的内容发生变化,具体分析如下;

对象切割分析

我们知道,派生类通常会比基类大,因为派生类不仅有基类的成员还有派生类本身的成员,经过向上转换(派生类对象强制转换为基类对象),就会造成对象内容被切割(object slicing).

当代码执行到((CAnimal)FishObj).breath()语句时,发生了object slicing,其过程如下:

这里写图片描述
图二 对象切割流程

备注:本例中m_data1和m_data2是虚拟的,只有vptr是真实的。

从图中可以看出,当发生强制转换时有两个步骤:

  1. 发生CAnimal对象构造,同时将vptr中的值被赋值为CAnimal::breath的地址
  2. ((CAnimal)FishObj).breath()调用转换为临时对象的breath调用。

我们也可以从汇编代码中看出实际执行情况,汇编代码如下:

这里写图片描述
图三 代码汇编分析

在汇编代码中也验证了图二流程分析的正确性,需要注意的是在强制转换过程中,编译器会主动为我们合成一个构造函数,不是我们定义的那个构造函数,但调用的析构函数都是同一个

通过以上分析,用例4的测试结果也就不感到意外了。

总结

如果类对象发生了切割或者向上强制转换,会产生临时对象,使得这个临时对象变成真正的CAnimal类对象,而不是CFish对象。

在分析问题过程中,也了解到一个类的构造函数有多个,但是其析构函数只有一个,因为析构函数没有返回值,没入参,也就无法实现析构函数的重载。

原创粉丝点击