C++对象模型分析-继承原理解析

讨论主题:分析C++继承关系的实现原理。

 

继承关系下的构造和析构

构造由内而外。先调用基类Base的构造函数,但后执行自己的构造函数。

析构由外而内。先析构自己,再调用Base的析构函数。

值得注意的,base class的析构函数需要申明为virtual,否则在析构时,派生类仅析构基类,而析构自身,可能导致内存泄漏。

class Base{

       public:

              Base(){

                     cout << “Base constructor” << endl;

}

virtual ~Base(){

       cout << “Base destructor” << endl;

}

};

class Derived : public Base{

       public:

              Derived() : Base() {

                     cout << “Derived constructor” << endl;

}

~Derived(){

       cout << “Derived destructor” << endl;

}

};

{

       Base* p = new Derived();

       delete p;

}

根据上诉分析,打印结果为:

Base constructor

Derived constructor

Derived destructor

Base destructor

若基类的析构函数未申明为virtual,则delete p时不会执行Derived析构函数。执行结果为:

Base constructor

Derived constructor

Base destructor

如果在派生类中动态分配了内存,最后执行析构函数中释放内存操作,则造成内存泄漏。

为什么声明为virtual就可以执行派生类的析构函数???

继承+复合关系下的构造和析构

派生类Derived不仅继承Base,还包含Component对象。此时,构造Derived对象时,先构造Base,后构造Component,最后构造Derived。析构时,顺序和构造方向相反。

class Base{

       public:

              Base() {

                     cout << “Base constructor” << endl;

}

virtual ~Base() {

       cout << “Base destructor” << endl;

}

};

class Component {

       public:

              Component() {

                     cout << “Component constructor” << endl;

}

~ Component() {

                     cout << “Component destructor” << endl;

}

};

class Derived : public Base {

       private:

              Component c;

       public:

              Derived() : Base() {

                     cout << “Derived constructor” << endl;

}

~Derived() {

       cout << “Derived destructor” << endl;

}

};

{

       Base* p = new Derived();

       delete p;

}

上诉的分析得出打印的结果为:

Base constructor

component constructor

Derived constructor

Derived destructor

component destructor

Base destructor

 

虚指针和虚表

含有virtual修饰的类成员函数时,会多分配4bytes作为虚表指针,位于类对象的首部。

虚表中全是函数指针,按照类中虚函数的声明顺序依次存放,并以‘\0’结束。

#include <iostream>

using namespace std;



class A{

private:

    int m_data1, m_data2;

public:

    virtual void vfunc1(){};

    virtual void vfunc2(){};

    void func1(){};

    void func2(){};

};



class B : public A{

private:

    int m_data3;

public:

    virtual void vfunc1(){};

    void func2(){};

};



class C : public B{

private:

    int m_data1, m_data4;

public:

    virtual void vfunc1(){};

    void func2(){};

};



int main()

{

    A* ptrA = new A();

    B* ptrB = new B();

    C* ptrC = new C();



    cout << "A size: " << sizeof(*ptrA) << endl;

    cout << "B size: " << sizeof(*ptrB) << endl;

    cout << "C size: " << sizeof(*ptrC) << endl;



    cout << "A vptr: " << ptrA << endl;

    cout << "B vptr: " << ptrB << endl;

    cout << "C vptr: " << ptrC << endl;



    unsigned int *vtblA = (unsigned int *)(*(unsigned int *)ptrA);

    unsigned int *vtblB = (unsigned int *)(*(unsigned int *)ptrB);

    unsigned int *vtblC = (unsigned int *)(*(unsigned int *)ptrC);



    cout << "A::vfunc1 addr: " << hex << vtblA[0] << endl;

    cout << "B::vfunc1 addr: " << hex << vtblB[0] << endl;

    cout << "C::vfunc1 addr: " << hex << vtblC[0] << endl;



    cout << "A::vfunc2 addr: " << hex << vtblA[1] << endl;

    cout << "B::vfunc2 addr: " << hex << vtblB[1] << endl;

    cout << "C::vfunc2 addr: " << hex << vtblC[1] << endl;



    cout << "A::vbtl_end: " << hex << vtblA[2] << endl;

    cout << "B::vbtl_end: " << hex << vtblB[2] << endl;

    cout << "C::vbtl_end: " << hex << vtblC[2] << endl;



    delete ptrA;

    delete ptrB;

    delete ptrC;



    return 0;

}

在上例中,类间关系为:类A为基类,类B继承A,类C继承B。

类大小:类A中有2个int成员变量并存在虚函数,大小为4*2+4=12bytes。

类B继承A,且B私有1个int成员变量,大小为12+4=16bytes。

类C继承B,私有2个int成员变量,大小为16+4*2=24bytes。

类间虚函数关系,类A两个虚函数vfunc1(),vfunc2();类B覆盖了vfunc1(),继承vfunc2();

类C也覆盖了vfunc1(),继承了vfunc2()。故,在虚表中,vbtl[1]应该指向同一个vfunc2(),也就是基类的vfunc2(),但vbtl[0]各自指向自己的vfunc1()。

测试结果为:

this指针

调用非静态成员函数,传参时总会带有this指针。这是实现动态绑定,实现多态的基础。

Applicaion framework

CDocument :: OnFileOpen(){

       …

       Serialize();      //this->Serialize() == (*(this->vptr)[n])(this)

       …

}

virtual Serialize();

Application

Class CMyDoc : Public CDocument

{

       virtual Serialize() { … }

}

main(){

       CMyDoc myDoc;

       myDoc.OnFileOpen();

}

主程序main中,定义对象myDoc,随后通过该对象调用OnFileOpen。实际上可以翻译成

CDocument::OnFileOpen(&myDoc);  //&myDoc相当于this

在OnFileOpen()中,进一步调用虚函数Serialize(),此时的this指向myDoc。所以通过this得到虚表指针,然后调用虚表中Serialize函数。这也实现了指针指向谁,就调用谁的虚函数。

Dynamic Binding,动态绑定

在C中函数调用都是静态绑定,在编译器时,就确定了函数入口地址,改变不了。这也就没办法实现多态。要实现动态绑定,在运行时,在确定函数入口地址,则需要额外的参数,它就是this指针。

B b;

A a = (A)b;

a.vfunc1();

A* pa = new B;

pa->vfunc1();

pa = &b;

pa->vfunc1();

借用上面A、B、C得继承关系。a.vfunc1()这属于静态绑定,调用call xxx_addr。因为在编译是已经确定了a.vfunc1()中this就是&a。而要实现多态,需要通过引用或指针的方式。下面两个pa->vfunc1()都属于动态绑定。调用call dword ptr [edx],不是call绝对地址。

为什么需要指针或引用调用虚函数才能实现多态???

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值