zoukankan      html  css  js  c++  java
  • C++中多态的实现原理

    多态是面向对象的基本特征之一。而虚函数是实现多态的方法。那么virtual function到底如何实现多态的呢?

    1 基类的内存分布情况
    请看下面的sample

    class A
    {
    void g(){.....}
    };
    则sizeof(A)=1;
    如果改为如下:
    class A
    {
    public:
        virtual void f()
        {
           ......
        }
        void g(){.....}
    }
    则sizeof(A)=4! 这是因为在类A中存在virtual function,为了实现多态,每个含有virtual function的类中都隐式包含着一个静态虚指针vfptr指向该类的静态虚表vtable, vtable中的表项指向类中的每个virtual function的入口地址
    例如 我们declare 一个A类型的object :
        A c;
        A d;
    则编译后其内存分布如下:
    image
    从 vfptr所指向的vtable可以看出,每个virtual function都占有一个entry,例如本例中的f函数。而g函数因为不是virtual类型,故不在vtable的表项之内。说明:vtab属于类成员静态pointer,而vfptr属于对象pointer

    2 继承类的内存分布状况
    假设代码如下:
    public B:public A
    {
    public :
        int f() //override virtual function
        {
            return 3;
        }
    };


    A c;
    A d;
    B e;
    编译后,其内存分布如下:
    image

    从中我们可以看出,B类型的对象e有一个vfptr指向vtable address:0x00400030 ,而A类型的对象c和d共同指向类的vtable address:0x00400050a


    3 动态绑定过程的实现
        我们说多态是在程序进行动态绑定得以实现的,而不是编译时就确定对象的调用方法的静态绑定。
        其过程如下:
        程序运行到动态绑定时,通过基类的指针所指向的对象类型,通过vfptr找到其所指向的vtable,然后调用其相应的方法,即可实现多态。
    例如:
    A c;
    B e;
    A *pc=&e; //设置breakpoint,运行到此处
    pc=&c;
    此时内存中各指针状况如下:
    image
    可以看出,此时pc指向类B的虚表地址,从而调用对象e的方法。

    继续运行,当运行至pc=&c时候,此时pc的vptr值为0x00420050,即指向类A的vtable地址,从而调用c的方法。
    这就是动态绑定!(dynamic binding)或者叫做迟后联编(lazy compile)。

     

    为了更加透析多态的原理,我们可以debug 程序在runtime时候的对象内存分布情况。

    以下面这段简单的程序为例

    // SimpleStack.cpp : Defines the entry point for the console application.
    //

    #include
    "stdafx.h"

    class Base
    {
    public
    :
       
    int
    m_data;
       
    static int
    m_staticvalue;
        Base(
    int
    data)
        {
            m_data
    =
    data;
        }
       
    virtual void
    DoWork()
        {
        }
    };

    class
    AnotherBase
    {
    public
    :
       
    virtual void
    AnotherWork()
        {}
       
    };

    class DerivedClass:public Base,public
    AnotherBase
    {
    public
    :
        DerivedClass(
    int
    t_data):Base(t_data)
        {}

       
    virtual    void
      DoWork()
        {
        }

       
    virtual void
    AnotherWork()
        {
        }
    };

    int Base::m_staticvalue=1
    ;

    int main(int argc, char*
    argv[])
    {
       
        DerivedClass b(
    1
    );
        b.DoWork();

       
    return 0
    ;
    }


     

    当程序运行后我们设置很简单的breakpoint: bp simplestack!derivedclass::dowork. 断点命中后的call stack如下:

    0:000> kb
    ChildEBP RetAddr  Args to Child             
    0012ff20 0040102a 00daf6f2 00daf770 7ffd7000 SimpleStack!
    DerivedClass::DoWork
    0012ff80 004012f9 00000001 00420e80 00420dc0 SimpleStack!main+0x2a
    0012ffc0 7c817077 00daf6f2 00daf770 7ffd7000 SimpleStack!mainCRTStartup+0xe9
    0012fff0 00000000 00401210 00000000 78746341 kernel32!BaseProcessStart+0x23


    这时,我们可以看看DerivedClass对象的内存内分布情况:

    0:000> dt SimpleStack!DerivedClass 0012ff74
       +0x000 __VFN_table : 0x0040c020  //指向虚表的指针1
       +0x004 m_data           : 1
       =0040d030 Base::m_staticvalue : 1  //(类成员)
       +0x008
    __VFN_table : 0x0040c01c  //指向虚表的指针2

    可以看到,DerivedClass对象中包含两个指向虚表的指针,地址分别为0x0040c020 和0x0040c01c 。一个为指向override了BaseClass的方法的虚表,一个指向orverride了AnotherBase方法的虚表。

    可以查看对应虚表中的方法:

    0:000> dds 0x0040c01c
    0040c01c  00401140 SimpleStack!DerivedClass::AnotherWork
    0040c020  00401110 SimpleStack!DerivedClass::DoWork
    0040c024  004010e0 SimpleStack!Base::DoWork
    0040c028  004011a0 SimpleStack!AnotherBase::AnotherWork
    ......

    通过以上分析,应该可以透析多态的本质了。

    这种看内存分配方案真的不错的,^-^

     

    自己也试了一下,虽然常用VC,不过也没注意过

    #include <iostream>
    using namespace std;
    class base2
    {
    public:
    virtual std::string ff(){return "base2";}
    protected:
    private:
    };
    class base
    {
    public:
    virtual std::string f1(){return "base";}
    virtual std::string f2(){return "base";}
    virtual std::string f3(){return "base";}
    protected:
    private:
    };
    class child : public base , public base2
    {
    public:
    virtual std::string f2(){return "child";}
    virtual std::string f3(){return "child";}
    protected:
    private:
    };
    class grant : public child
    {
    public:
    virtual std::string f3(){return "grant";}
    protected:
    private:
    };
    void main()
    {
    base a;
    child b;
    grant c;
    string sa = a.f1();
    string sb = b.f1();
    string sc = c.f1();
    }
    // 内存状态
    - a {...}
    - __vfptr 0x0042f024 const base::`vftable'
    [0x0] 0x00401014 base::f1(void)
    [0x1] 0x00401055 base::f2(void)
    [0x2] 0x00401032 base::f3(void)
    - b {...}
    - base {...}
    - __vfptr 0x0042f038 const child::`vftable'{for `base'}
    [0x0] 0x00401014 base::f1(void)
    [0x1] 0x00401050 child::f2(void)
    [0x2] 0x0040106e child::f3(void)
    - base2 {...}
    - __vfptr 0x0042f034 const child::`vftable'{for `base2'}
    [0x0] 0x00401023 base2::ff(void)
    - c {...}
    - child {...}
    - base {...}
    - __vfptr 0x0042f05c const grant::`vftable'{for `base'}
    [0x0] 0x00401014 base::f1(void)
    [0x1] 0x00401050 child::f2(void)
    [0x2] 0x0040103c grant::f3(void)
    - base2 {...}
    - __vfptr 0x0042f058 const grant::`vftable'{for `base2'}
    [0x0] 0x00401023 base2::ff(void)

  • 相关阅读:
    操作系统的用户态和内核态
    C++程序编译过程
    大爽Python入门练习题 15 最长字符串
    大爽Python入门练习题 25 二维列表行列与序数关系
    大爽Python入门练习题 16 三个数找中间值
    大爽Python入门练习题 17 最大差值
    大爽Python入门练习题 19 猜结果
    大爽Python入门练习题 11 倒序生成列表
    大爽Python入门练习题 18 字母次数统计
    大爽Python入门练习题 110 猜函数
  • 原文地址:https://www.cnblogs.com/dongzhiquan/p/2223251.html
Copyright © 2011-2022 走看看