关于类的虚函数表

简介:     多态是面向对象的特性,通俗说,即用父类指针调用某成员函数时,针对不同对象调用的是不同的函数。从语言层面上看的统一形式的调用(相同名称的虚函数),体现出个性化的行为。 C++ 对象的多态通过虚函数表来实现的,虚函数表属于 C++ 对象模型层面的东西。

    多态是面向对象的特性,通俗说,即用父类指针调用某成员函数时,针对不同对象调用的是不同的函数。从语言层面上看的统一形式的调用(相同名称的虚函数),体现出个性化的行为。 C++ 对象的多态通过虚函数表来实现的,虚函数表属于 C++ 对象模型层面的东西。

    对于一个具体类型来说,它的行为是确定的,举一个简单例子,父类是几何形状,它有一个虚方法是计算自己的面积。对于具体类型例如矩形,正方形,圆形等,他的计算面积的方法是确定的。即对于同一个类型的所有实例,它们的虚函数表的内容相同,在编译时可以确定。

    因此,如果把虚函数表的全部内容附着在对象实例上,这样的对象模型显然是浪费内存的。因此,在对象实例起始处,放的是一个指针,指向其虚函数表,因此每个对象的虚函数表在实例中仅占一个指针(4 bytes)空间。如果一个类型有多个实例,它们指向的是同一份虚函数表(典型情况是位于进程空间的 .rdata section)。虚函数表指针的初始化由编译器生成的各种构造函数负责。如果一个函数不包含虚函数,则对象实例中不包含虚函数表指针。

    虚函数表可以认为是由函数指针组成的数组,数组元素由该类型的所有虚函数的地址组成,用 NULL 表示结尾(取决于编译器对模型的实现)。如果该类型实现了自己的虚函数,它将覆盖从父类继承下来的元素。

    编译器知道表中每个元素对应是那个虚函数,因此调用时取出元素,通过 call 指令实现调用。观察 VC6 debug 版本的汇编代码,虚函数表的内容被编译到只读的 section(和其他常量字符串一起),每个元素代表的是函数的地址(这些元素是代码段起始部的一组 jump 语句的地址,类似中断向量表,用于跳到真正的函数体)。由于虚函数表位于只读 section 中,所以其元素(函数指针)是不能直接改写的。

 

    下面的代码演示,交换两个实例的虚函数表,从而这两个对象的行为表现将被“交换”。

test_code
#include <stdio.h>

class ClassA
{
public:
    int a;

    ClassA()
    {
        printf("constructor(void).\n");
    }

    ClassA(ClassA& t)
    {
        a = t.a;
        printf("copy constructor.\n");
    }

    ClassA& operator =(ClassA& t)
    {
        a = t.a;
        printf("asignment operator.\n");
        return *this;
    }

    virtual void say()
    {
        printf("ClassA::say();\n");
    }

    virtual void say2()
    {
        printf("hello world2.\n");
    }
};

class ChildClassB : public ClassA
{
public:
    virtual void say()
    {
        printf("B::say();\n");
    }
};

class ChildClassC : public ClassA
{
public:
    virtual void say()
    {
        printf("C::say();\n");
    }
};


int main(int argc, char* argv[])
{
    ClassA *p1 = new ChildClassB();
    ClassA *p2 = new ChildClassC();

    p1->say();
    p2->say();

    unsigned int *p3 = (unsigned int*)p1;
    unsigned int *p4 = (unsigned int*)p2;

    printf("p1's vt: %08X\n", *p3);
    printf("p2's vt: %08X\n", *p4);

    //交换两个对象的虚函数表地址
    unsigned int tmp = *p3;
    *p3 = *p4;
    *p4 = tmp;

    p1->say();
    p2->say();


    delete p1;
    delete p2;
    return 0;
}

 

    下图是根据 VC6 Debug 版本的代码给出的是 ChildClassB 的对象模型(从《Inside The C++ Object Model》图1.3 修改而来):

    

 

    从实现机制来看,调用虚函数需要经过运行时的寻址,因此效率不如可以在编译时解析完成的非虚函数。如果不使用多态,即不使用虚函数,仅仅使用 class 进行数据封装和继承的程序风格,则称为“基于对象”(object-based)。

目录
相关文章
|
安全 Shell Linux
【Shell 命令集合 系统管理 】⭐⭐⭐Linux 重启 reboot命令 使用指南
【Shell 命令集合 系统管理 】⭐⭐⭐Linux 重启 reboot命令 使用指南
1875 0
|
6月前
|
人工智能 大数据
AI赋能,短剧行业迎来创新发展新热潮
随着AI技术的快速迭代,短剧行业正经历一场深刻的创新变革,从创作、制作到传播全流程被重构。AI技术大幅降低创作门槛、压缩制作周期、控制生产成本,推动短剧从“量的积累”向“质的提升”转型,催生AI仿真人漫剧等新形态。然而热潮之下,内容同质化、情感表达不足、版权风险等问题也随之凸显。唯有平衡技术赋能与内容初心,才能让AI真正助力短剧行业实现可持续创新发展。
|
9月前
|
人工智能 搜索推荐 测试技术
2026年Geo优化师选师指南:学习Geo应该找哪位专家老师?
随着AI重塑搜索生态,GEO(生成式引擎优化)成为企业增长新刚需。2026年,全球市场规模将达380亿元,但超半数企业面临效果难量化、优化不稳定等挑战。IDC数据显示,仅30%企业实现可衡量增长。在此背景下,具备E-E-A-T权威标准与实战能力的导师至关重要。于磊老师首创“两大核心+四轮驱动”体系,融合人性化内容与可信验证,助力传统制造企业询盘增长120%,打造AI时代可持续获客范本,被公认为最具普适性与前瞻性的GEO领路人。
493 5
|
8月前
|
存储 安全 数据处理
【需求:GDPR合规下做地域定向】解决方案:仅用IP离线库输出国家码,不存原始IP?
在GDPR合规要求下,技术部通过IP离线库实现“仅输出国家码、不存原始IP”的地域定向方案。该方案确保IP仅作瞬时输入,本地解析后不留存,符合数据最小化原则。相比在线API,离线库避免第三方数据共享,边界清晰,合规可解释性强,且性能稳定,适合高并发场景。结合严格日志管控与工程实践,此方案在保障业务需求的同时,有效降低合规风险,为海外业务提供安全可控的基础支持。
|
存储 关系型数据库 MySQL
数据仓库设计
数据仓库设计
447 3
|
大数据 数据处理 数据安全/隐私保护
数据治理,你真的合规了吗?——从代码到实践的深度解析
数据治理,你真的合规了吗?——从代码到实践的深度解析
487 8
|
测试技术 API 开发者
使用 Playwright 脚本录制简化自动化测试:完全指南
Playwright提供了一种脚本录制功能,帮助开发者通过交互式操作自动生成测试脚本,提升测试效率。使用Playwright的命令行工具`codegen`,如`playwright codegen python &lt;url&gt;`,可以记录浏览器操作并生成Python测试脚本。生成的脚本使用Playwright API模拟用户交互,如`page.click()`和`page.fill()`。这种方法简化了自动化测试脚本的编写,促进了测试流程的加速。
|
机器学习/深度学习 人工智能 自然语言处理
探索人工智能前沿:自然语言处理的最新进展
探索人工智能前沿:自然语言处理的最新进展
1390 3
|
数据中心
网络拓扑包括哪些类型?
【8月更文挑战第19天】网络拓扑包括哪些类型?
1514 1
基于matlab的风力发电系统建模与详细性能仿真分析
本研究介绍风力发电原理与系统模型,使用MATLAB 2022a进行性能仿真。风力通过风轮转化为电能,涉及贝努利定理及叶素理论。仿真展示了风速与输出功率间的关系,包括风电利用系数、切入切出控制与MPPT控制效果。当风速超过25m/s时,系统自动停机保护设备。MPPT算法确保了在变化风速下获得最大功率。

热门文章

最新文章