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)

  • 相关阅读:
    [ERR] Node 10.211.55.8:7001 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
    PAT A1137 Final Grading (25 分)——排序
    PAT A1136 A Delayed Palindrome (20 分)——回文,大整数
    PAT A1134 Vertex Cover (25 分)——图遍历
    PAT A1133 Splitting A Linked List (25 分)——链表
    PAT A1132 Cut Integer (20 分)——数学题
    PAT A1130 Infix Expression (25 分)——中序遍历
    PAT A1142 Maximal Clique (25 分)——图
    PAT A1141 PAT Ranking of Institutions (25 分)——排序,结构体初始化
    PAT A1140 Look-and-say Sequence (20 分)——数学题
  • 原文地址:https://www.cnblogs.com/dongzhiquan/p/2223251.html
Copyright © 2011-2022 走看看