C++中多态本质 实例分析

本文深入探讨了C++中多态的实现机制,详细解释了虚函数如何帮助实现多态,包括基类与派生类中的虚函数表(vtable)布局、内存分布情况以及动态绑定的过程。

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;
则编译后其内存分布如下:

从vfptr所指向的vtable可以看出,每个virtual function都占有一个entry,例如本例中的f函数。而g函数因为不是virtual类型,故不在vtable的表项之内。说明:vtab属于类成员静态pointer,而vfptr属于对象pointer
2 继承类的内存分布状况
假设代码如下:

 class B:public A
{
public :
    void f() //override virtual function
    {
        return ;
    }
};



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


从中我们可以看出,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;
此时内存中各指针状况如下:

可以看出,此时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
......

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

<think>好的,我现在要详细分析用户的问题,了解C++中动态多态的底层实现机制,特别是vtable的原理。首先,我需要回忆自己对C++多态的理解,并查阅提供的引用资料来确保准确性。 用户的问题集中在动态多态的底层机制,特别是虚函数表(vtable)和虚函数表指针(vptr)。根据引用[2]和[3],动态多态是通过虚函数表实现的。当类中存在虚函数时,编译器会为该类生成一个虚函数表,其中存放虚函数的地址。每个对象在实例化时会有一个指向该表的vptr。 接下来需要考虑vptr的存储位置。根据引用[3]中的例子,Base类的对象b包含虚函数,因此对象的内存布局中会有一个vptr,指向Base的虚函数表。这个vptr通常位于对象内存布局的最前面,之后是类的成员变量。 继承情况下的vtable如何变化?例如,如果有一个派生类继承自基类,并重写虚函数,那么派生类的vtable会包含基类的虚函数,但被重写的函数地址会被替换为派生类的实现。引用[5]提到,构造过程中vptr会从基类到派生类逐步更新。这解释了派生类对象在构造时如何正确初始化vptr,确保多态调用时使用正确的函数。 动态多态的具体调用过程是怎样的?当通过基类指针或引用调用虚函数时,程序会通过对象的vptr找到对应的虚函数表,再根据函数在表中的位置调用正确的函数。例如,引用[2]中的例子,基类指针调用performTask和recharge时,实际执行的是派生类中的实现,这就是通过vtable查找到的。 还需要考虑虚析构函数的重要性。如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,可能只会调用基类的析构函数,导致资源泄漏。因此,基类析构函数通常应声明为虚函数。 此外,虚函数表的结构和内存布局可能受编译器实现的影响,但大多数编译器遵循相似的模式。每个类有自己的vtable,所有该类的对象共享同一个vtable。而每个对象有自己的vptr,指向对应的vtable。 可能的疑问点:多重继承下的vtable如何处理?这种情况下,每个基类可能有自己的vtable,派生类对象可能包含多个vptr,指向不同的vtable。这可能导致更复杂的内存布局,但基本机制仍然是使用vptr和vtable来实现动态绑定。 总结来说,动态多态的底层机制依赖于虚函数表和虚函数表指针。编译器在编译时生成虚函数表,运行时通过对象的vptr找到正确的函数地址,实现多态调用。</think>### C++动态多态底层实现机制详解 C++的动态多态通过**虚函数表(vtable)**和**虚函数表指针(vptr)**实现,以下是其核心原理的分步解析: --- #### 1. **虚函数表(vtable)的生成** - **触发条件**:当类中声明至少一个虚函数时,编译器会为该类生成一个虚函数表(vtable)。 - **内容**:vtable是一个函数指针数组,存储该类所有虚函数的实际地址。例如: ```cpp class Base { public: virtual void func1() { /*...*/ } // 虚函数 virtual void func2() { /*...*/ } }; ``` 此时,`Base`类的vtable包含两个条目:`&Base::func1`和`&Base::func2`[^3]。 --- #### 2. **虚函数表指针(vptr)的存储** - **对象内存布局**:每个包含虚函数的类的实例化对象中,编译器会隐式插入一个指针`vptr`,指向对应的vtable。 - 例如,`Base b;`对象内存首部存储`vptr`,其后是成员变量(如`int _b`和`char _a`)[^3]。 - **继承关系下的vptr更新**: - 派生类构造时,会从基类到派生类逐步更新`vptr`。例如,`Derived`类对象构造时,先调用基类构造函数(此时`vptr`指向基类vtable),再调用派生类构造函数(`vptr`更新为指向派生类vtable)[^5]。 --- #### 3. **动态绑定的实现过程** 当通过**基类指针或引用**调用虚函数时: 1. 通过对象的`vptr`找到对应的vtable。 2. 从vtable中取出目标虚函数的地址。 3. 调用该地址对应的函数。 **示例**: ```cpp class CleaningRobot : public Robot { public: void performTask() override { /* 清洁逻辑 */ } // 重写基类虚函数 }; Robot* robot = new CleaningRobot(); robot->performTask(); // 实际调用CleaningRobot::performTask() ``` 此时,`robot->performTask()`会根据`CleaningRobot`对象的`vptr`找到派生类的vtable,并调用其函数实现[^2][^5]。 --- #### 4. **虚函数表的结构(以单继承为例)** | 虚函数表条目 | 函数地址 | |--------------|------------------------| | 第0项 | `&CleaningRobot::performTask` | | 第1项 | `&CleaningRobot::recharge` | - **多态调用本质**:函数调用被编译为类似`(*(vptr[n]))(this)`的指令,其中`n`是虚函数在表中的索引。 --- #### 5. **多重继承的复杂场景** - **多个vptr**:若派生类继承自多个基类(每个基类有虚函数),则派生类对象会包含多个`vptr`,分别指向不同基类的vtable。 - **虚基类**:虚继承时可能引入额外的间接层(如虚基类指针),但核心仍依赖vtable机制。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

developer_wgl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值