C++中多态的实现原理

2021年09月15日 阅读数:6
这篇文章主要向大家介绍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;
则编译后其内存分布以下:
C++中多态的实现原理_内存分布
从 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;
编译后,其内存分布以下:
C++中多态的实现原理_虚表_02

从中咱们能够看出,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;
此时内存中各指针情况以下:
C++中多态的实现原理_虚表_03
能够看出,此时pc指向类B的虚表地址,从而调用对象e的方法。

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

 

为了更加透析多态的原理,咱们能够debug 程序在runtime时候的对象内存分布状况。app

如下面这段简单的程序为例ide

// 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
spa


这时,咱们能够看看DerivedClass对象的内存内分布状况:debug

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方法的虚表。对象

能够查看对应虚表中的方法:blog

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)