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

背景

现在有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;
    }
};
//继承类CFish
class 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 Constructor
CFish Constructor
//new CFish new对象时输出
CAnimal Constructor
CFish Constructor

case test begin...

case1
CFish breath
case2
CFish breath
case3
CFish breath

case4
CAnimal breath      //------> 出乎意外,竟不是CFish breath
CAnimal Destructor  //------> 出乎意外,竟有调用析构函数
case test end...

//函数返回栈对象析构
//以及delete对象析构
CFish Destructor
CAnimal Destructor
CFish Destructor
CAnimal 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是真实的。

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

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

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

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

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

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

总结

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值