C++中如何实现多态?(虚函数表与动态绑定)

发布时间 - 2026-01-23 00:00:00    点击率:
只有被virtual显式修饰的成员函数,且通过指针或引用调用时,才触发动态绑定;普通函数、静态函数、构造函数及非virtual析构函数均不参与虚函数机制。

虚函数怎么声明才触发动态绑定

只有被 virtual 显式修饰的成员函数,且通过指针或引用调用时,才会走运行时动态绑定。普通函数、静态成员函数、构造函数、析构函数(除非显式声明为 virtual)都不参与虚函数机制。

常见错误是忘记在基类中加 virtual,比如写成 void func() {} 而不是 virtual void func() {},此时即使派生类重写,调用也仍是静态绑定,编译器直接绑定到指针/引用的静态类型所对应的函数。

实操建议:

  • 基类中所有预期被重写的接口,一律加上 virtual
  • 派生类中用 override 显式标注重写(C++11 起),避免拼写错误或签名不匹配导致意外隐藏而非重写
  • 纯虚函数写作 virtual void func() = 0;,含纯虚函数的类即为抽象类,不能实例化

虚函数表(vtable)在内存

里长什么样

每个含虚函数的类(或其子类)在编译期生成一张全局只读的函数指针表,即 vtable;每个该类的对象开头隐式插入一个指针 vptr,指向其所属类的 vtable。对象布局通常是:vptr + 成员变量。

注意:vtable 不是每个对象一份,而是每类一份;vptr 才是每个对象一份。多继承下可能有多个 vptr(如菱形继承需 virtual 继承来解决)。

典型布局示例(单继承):

class Base {
public:
    virtual void f() { cout << "Base::f"; }
    int x;
};

class Derived : public Base {
public:
    void f() override { cout << "Derived::f"; }
    int y;
};

// sizeof(Derived) 通常是 16(x + y + vptr,假设指针占 8 字节)

调试时可通过打印对象地址和 *reinterpret_cast(&obj) 查看 vptr 指向的 vtable 内容(需关闭优化,且依赖 ABI,仅作理解参考)。

为什么构造函数和析构函数不能是虚函数(除了析构函数例外)

构造函数不能是虚函数,是因为对象尚未完成构造,vptr 还没被初始化到最终类的 vtable —— 它在构造过程中会随继承链逐级更新:先调基类构造,设基类 vtable 地址;再调派生类构造,覆盖为派生类 vtable 地址。此时若允许虚调用,会调到不完整状态的函数,语义危险。

析构函数可以且**应该**是 virtual 的(尤其基类有虚函数时),否则通过基类指针 delete 派生类对象,只会调基类析构,派生部分内存泄漏。

关键点:

  • 基类析构函数必须声明为 virtual,哪怕函数体为空
  • 一旦类设计为多态基类(即有 virtual 函数),就默认应有 virtual 析构函数
  • 不要在构造/析构函数中调用虚函数 —— 此时动态绑定失效,实际调用的是当前正在构造/析构的那个类的版本

动态绑定失败的三个典型场景

即使写了 virtualoverride,仍可能因调用方式不对而退化为静态绑定。

最容易忽略的是:通过对象值(非指针/引用)调用虚函数,例如 Base b = Derived(); b.func(); —— 发生对象切片(slicing),b 是纯 Base 对象,vptr 指向 Base vtable,调用 Base::func

其他常见陷阱:

  • 使用 static_cast 强转指针类型后调用,如 static_cast(ptr)->func(),若 ptr 实际是 Derived*,但强制按 Base* 解释,仍能正确动态绑定;但若强转为无关类型(如 Other*),则未定义行为
  • 函数参数是值传递而非引用/指针,传入派生类对象会切片,丢失虚函数信息
  • 模板函数内直接调用 t.func()t 是模板参数),编译期绑定,与虚函数无关

虚函数机制只对“指针或引用 + 成员函数调用”这一特定语法生效,其余都是普通函数调用。这点必须刻进本能。


# c++  # 多态  # 成员变量  # 成员函数  # 子类  # 构造函数  # 析构函数  # 引用调用  # void  # 指针  # 继承  # 多继承  # 虚函数  # 纯虚函数  # 接口  # 指针类型  # 值传递  # 切片  # delete  # 对象  # 绑定  # 重写  # 派生类  # 的是  # 或引用  # 而非  # 类中  # 都是  # 这一  # 是因为 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Python并发异常传播_错误处理解析【教程】  如何获取PHP WAP自助建站系统源码?  iOS UIView常见属性方法小结  教你用AI将一段旋律扩展成一首完整的曲子  Android使用GridView实现日历的简单功能  如何挑选高效建站主机与优质域名?  Android自定义控件实现温度旋转按钮效果  Laravel PHP版本要求一览_Laravel各版本环境要求对照  清除minerd进程的简单方法  Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解  ,怎么在广州志愿者网站注册?  详解vue.js组件化开发实践  HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】  Laravel如何处理文件下载请求?(Response示例)  如何在云主机快速搭建网站站点?  英语简历制作免费网站推荐,如何将简历翻译成英文?  Laravel如何操作JSON类型的数据库字段?(Eloquent示例)  如何在搬瓦工VPS快速搭建网站?  如何快速搭建高效服务器建站系统?  悟空浏览器如何设置小说背景色_悟空浏览器背景色设置【方法】  如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框  小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?  Laravel Fortify是什么,和Jetstream有什么关系  logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?  如何在Windows虚拟主机上快速搭建网站?  免费视频制作网站,更新又快又好的免费电影网站?  微信小程序 五星评分(包括半颗星评分)实例代码  php485函数参数是什么意思_php485各参数详细说明【介绍】  Windows Hello人脸识别突然无法使用  Laravel如何集成Inertia.js与Vue/React?(安装配置)  哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?  Laravel如何使用Passport实现OAuth2?(完整配置步骤)  JavaScript常见的五种数组去重的方式  Android滚轮选择时间控件使用详解  Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】  猎豹浏览器开发者工具怎么打开 猎豹浏览器F12调试工具使用【前端必备】  在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?  网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?  如何在阿里云完成域名注册与建站?  PHP 实现电台节目表的智能时间匹配与今日/明日轮播逻辑  Laravel Docker环境搭建教程_Laravel Sail使用指南  详解Android图表 MPAndroidChart折线图  Laravel如何将应用部署到生产服务器_Laravel生产环境部署流程  如何在阿里云虚拟主机上快速搭建个人网站?  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  Python图片处理进阶教程_Pillow滤镜与图像增强  C++时间戳转换成日期时间的步骤和示例代码  如何选择PHP开源工具快速搭建网站?  魔方云NAT建站如何实现端口转发?  如何用PHP快速搭建CMS系统?