依据C++对象模型和函数绑定机制解题

开始本篇文章之前,我们现在先来看看一个具体问题 :

class  CObject
{
public :
    
virtual   void  Serialize()
    {
        cout 
<<   " CObject::Serialize()  " ;
    }
};

class  CDocument :  public  CObject
{
public :
    
int  m_data1;
    
void  func1()
    {
        cout 
<<   " CDocument::func1() "   <<  endl;
        Serialize();
    }
    
virtual   void  Serialize()
    {
        cout 
<<   " CDocument::Serialize()  " ;
    }
    
void  func2()
    {
        cout 
<<   " CDocument::func2() "   <<  endl;
        test();
    }
    
void  test()
    {
        cout 
<<   " CDocument::test()  "
    }
};

class  CMyDoc :  public  CDocument
{
public :
    
int  m_data2;
    
virtual   void  Serialize()
    {
        cout 
<<   " CMyDoc::Serialize()  " ;
    }
    
void  test()
    {
        cout 
<<   " CMyDoc::test()  "
    }
};
    
void  main()
{
    cout 
<<   sizeof (CObject)  <<  endl;
    cout 
<<   sizeof (CDocument)  <<  endl;
    cout 
<<   sizeof (CMyDoc)  <<  endl;

    CMyDoc mydoc;
    CMyDoc
*  pmydoc  =   new  CMyDoc;
    CDocument
*  pdoc  =   new  CMyDoc;

    cout 
<<   " #1 testing "   << endl;
    mydoc.func1();
    mydoc.func2();
    mydoc.test();

    cout 
<<   " #2 testing "   << endl;
    pmydoc
-> func1();
    pmydoc
-> func2();
    pmydoc
-> test();

    cout 
<<   " #3 testing "   << endl;
    pdoc
-> func1();
    pdoc
-> func2();
    pdoc
-> test();

    cout 
<<   " #4 testing "   << endl;
    ((CDocument
* )( & mydoc)) -> func1();
    ((CDocument
* )( & mydoc)) -> func2();
    ((CDocument
* )( & mydoc)) -> test();   

    cout 
<<   " #5 testing "   << endl;
    ((CDocument)mydoc).func1();
    ((CDocument)mydoc).func2();
    ((CDocument)mydoc).test();        
}

如果题目的输出结果会使你头疼,你就有必要继续了解C++对象模型的内部细节和不同类型函数的调用本质。

这些知识点正是我想要讲述的内容。

首先让我们来了解一下包含有虚函数的类对象在内存中的结构:(在这里我只分析单继承的情况。至于多继承,请参考《Inside The C++ Object Model》
在Class1对象的内存区块中,包含一个指向虚函数表的指针以及对象的各个数据成员。至于Class1类的普通成员函数,他们在编译时被编译器更改名称,并增加了一个参数(this指针),因而可以处理调用者(C++对象)中的成员变量。所以在Class1对象的内存区块中看不到和成员函数相关的任何东西。
虚函数表的内容是依据类中的虚函数声明次序,一一填入函数指针。派生类会继承基类的虚函数表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚函数表就受了影响:表中元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。
接下来,需要了解虚成员函数和普通成员函数不同的绑定机制: 
虚成员函数采用动态绑定机制,也叫做运行时绑定。在执行期,依据调用者实际代表(或者说调用地址实际指向)的类型进行动态绑定。
普通的成员函数采用静态绑定机制,也叫做编译时绑定。在编译时,依据调用者的声明类型进行静态绑定。
有了这些基本概念之后,我们来分析上述问题的答案:
1)三个sizeof输出:
CObject的内存结构只有一个vptr; CDocument的内存结构包含一个vptr和数据成员m_data1; CMyDoc的内存结构包含一个vptr以及两个数据成员:继承来的m_data1和新定义的m_data2。所有的数据成员都是int型,在32位机上,指针占据的空间和int类型一样都是32个bit位---4个字节。sizeof输出的是字节数:
4
8
12
 
2)4个testing部分的输出: 
首先我们先要弄清楚调用者的声明类型和实际代表类型:
     // 声明类型:CMyDoc
    
// 实际类型:CMyDoc
    cout  <<   " #1 testing "   << endl;
    mydoc.func1();
    mydoc.func2();
    mydoc.test();

    
// 声明类型:CMyDoc
    
// 实际类型:CMyDoc
    cout  <<   " #2 testing "   << endl;
    pmydoc
-> func1();
    pmydoc
-> func2();
    pmydoc
-> test();

    
// 声明类型:CDocument
    
// 实际类型:CMyDoc
    cout  <<   " #3 testing "   << endl;
    pdoc
-> func1();
    pdoc
-> func2();
    pdoc
-> test();

    
// 经过类型转换之后
    
// 声明类型变为:CDocument
    
// 实际类型仍是:CMyDoc
    cout  <<   " #4 testing "   << endl;
    ((CDocument
* )( & mydoc)) -> func1();
    ((CDocument
* )( & mydoc)) -> func2();
    ((CDocument
* )( & mydoc)) -> test();   

    
// 经过对象切割之后
    
// 相当于编译器以所谓的拷贝构造函数把CDocument对象内容复制了一份,
    
// 使得在调用时mydoc完全变成了Cocument对象,包括vtable中的函数指针
    cout  <<   " #5 testing "   << endl;
    ((CDocument)mydoc).func1();
    ((CDocument)mydoc).func2();
    ((CDocument)mydoc).test();    
然后依据虚函数和普通函数不同的绑定机制去分析。
其中需要注意:CMyDoc自己没有func1和func2函数,而它继承了CDocument的所有成员。所以所有关于这两个函数的调用都是对CDocument::func1()和CDocument::func2的调用。
不同之处在于:func1中调用的虚函数需要在运行时依据调用者的实际代表类型进行动态绑定;func2中调用的普通成员函数----test在func2被编译的时候就需要具体绑定,此时只能从func2所在的类型----CDocument中开始查找,即使真正的声明类型CMyDoc中重写了test函数。因为当func2被编译的时候CMyDoc中的内容是不可见的。
由此,可以得到该部分的输出结果为:
# 1  testing
CDocument::func1()
CMyDoc::Serialize()
CDocument::func2()
CDocument::test()
CMyDoc::test()

#
2  testing
CDocument::func1()
CMyDoc::Serialize()
CDocument::func2()
CDocument::test()
CMyDoc::test()

#
3  testing
CDocument::func1()
CMyDoc::Serialize()
CDocument::func2()
CDocument::test()
CDocument::test()

#
4  testing
CDocument::func1()
CMyDoc::Serialize()
CDocument::func2()
CDocument::test()
CDocument::test()

#
5  testing
CDocument::func1()
CDocument::Serialize()
CDocument::func2()
CDocument::test()
CDocument::test()
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值