news 2026/10/19 4:24:07

虚函数表、RAII 与智能指针引用计数原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
虚函数表、RAII 与智能指针引用计数原理

虚函数表(vtable)与动态派发

基础概念:C++ 实现多态的底层机制,虚函数表是一个存放虚函数指针的数组,每个含有虚函数的类都会有一张虚函数表。虚指针 vptr(virtual pointer):对象里隐藏的指针成员,指向这个类对应的虚函数表。
对象布局 :编译器为每个多态类(含虚函数的类)生成一张静态只读的虚函数表(vtbl),按声明序存放各虚函数的函数指针(槽位 slot);每个对象实例头部内嵌一个 vptr ,指向其动态类型的vtable。
调用路径 :p->f() 被编译为两级间接寻址——解引用 vptr 取得 vtable,再按编译期确定的槽位索引取出函数指针间接调用,即 晚期绑定(late binding)/ 动态派发(dynamic dispatch)。
覆写语义 :派生类 override 在其 vtable 的 同槽位 覆写条目,未覆写槽位沿用基类;多重继承下一个对象可携带多组 vptr/vtable。
工程要点 :间接调用阻断内联,编译器会尝试 去虚化(devirtualization) ;RTTI的 type_info通常也挂载于 vtable。上图展示了派发链:Base* p(静态类型)指向 Derived 对象,调用经对象内 vptr 寻址到 Derived vtable,同槽位命中被覆写的&Derived::f 。

代码示例:

#include <iostream> struct Base { virtual void f() { std::cout << "Base::f\n"; } // 槽位 [0] virtual void g() { std::cout << "Base::g\n"; } // 槽位 [1] virtual ~Base() = default; // 基类析构必须为 virtual }; struct Derived : Base { void f() override { std::cout << "Derived::f\n"; } // 覆写槽位 [0],[1] 沿用 Base }; int main() { Base* p = new Derived; // 静态类型 Base*,动态类型 Derived p->f(); // 经 vptr 寻址 Derived vtable 槽位 [0] → Derived::f p->g(); // 槽位 [1] 未覆写 → Base::g delete p; // 经虚析构正确调用 ~Derived → ~Base std::cout << sizeof(Base) << '\n'; // 8(x64:一个 vptr,无数据成员) // 验证"同槽位覆写":两个 vtable 首槽位偏移一致,均为索引 0 }

RAII(Resource Acquisition Is Initialization资源获取即初始化)

基础概念:资源的获取放在构造函数,资源释放放在析构函数。依靠对象生命周期自动管理资源,避免内存泄漏、忘记关闭句柄。
核心语义 :将资源生命周期绑定到对象生命周期——构造函数**获取(acquire) 资源,析构函数 释放(release)**资源,所有权与栈上对象的自动存储期绑定。
保障来源 :C++ 的 确定性析构(deterministic destruction) 语义保证:无论是正常返回离开词法作用域,还是异常引发 栈展开(stack unwinding) ,已完全构造的自动对象的析构函数必然执行——这是**异常安全(exception safety,basic/strong guarantee)**的基石。
与 GC 对比 :释放时机确定、无 finalizer 式的延迟回收;典型载体如`ock_guard/unique_lock 、fstream、jthread及智能指针本身。

代码示例:

template<typename T> class MyRAII { private: T* ptr; public: explicit MyRAII(T* p) : ptr(p) {} // 获取资源 ~MyRAII() { delete ptr; } // 释放资源 // 禁止拷贝(防止两个对象管理同一块内存,二次释放) MyRAII(const MyRAII&) = delete; MyRAII& operator=(const MyRAII&) = delete; T& operator*() const { return *ptr; } T* operator->() const { return ptr; } }; void test() { MyRAII<int> p(new int(10)); // 不管函数正常返回还是抛出异常,离开作用域自动调用析构 delete }

示例图:

标准库RAII指针

std::unique_ptr
独占所有权,禁止拷贝,只能移动std::move
std::shared_ptr
共享所有权,引用计数,多个 shared_ptr 可以指向同一资源

以std::shared_ptr的代码为示例:

#include <iostream> #include <memory> struct Widget { ~Widget() { std::cout << "~Widget\n"; } }; int main() { auto p1 = std::make_shared<Widget>(); // 单次分配:对象 + 控制块 std::cout << p1.use_count() << '\n'; // 1 { auto p2 = p1; // 拷贝构造:控制块内 strong count 原子递增 std::cout << p1.use_count() << '\n'; // 2 } // p2 析构:strong 递减至 1,对象存活 std::cout << p1.use_count() << '\n'; // 1 } // p1 析构:strong 归零 → ~Widget; // weak 也归零 → 释放控制块 // 循环引用反例:strong 永不归零 → 内存泄漏 // struct Node { std::shared_ptr<Node> next; }; // 成环泄漏 // struct Node { std::weak_ptr<Node> next; }; // 用 weak_ptr 打破环路

以上便是本篇文章的全部内容,如果能帮到您的话,欢迎点赞收藏,也可以在评论区留下你的想法与疑问。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 4:24:01

MinerU解析本地文件(二)

一、精准解析 API&#xff08;需 Token&#xff0c;适合正式场景&#xff09;适用场景文件较大&#xff08;≤ 200MB&#xff0c;≤ 200 页&#xff09;需要高精度解析支持批量上传&#xff08;单次 ≤ 50 个&#xff09;核心接口步骤接口说明1POST /api/v4/file-urls/batch申请…

作者头像 李华
网站建设 2026/10/19 4:23:02

Handsontable入门:5分钟实现网页可编辑Excel表格

简介&#xff1a;Handsontable 是一款轻量级、高性能的 JavaScript 数据网格组件&#xff0c;专为 Web 应用中实现 Excel 风格的表格编辑与数据交互而设计&#xff0c;适用于前端开发者、数据可视化工程师及需要嵌入可编辑表格的中后台系统项目。资源包为官方 dist 目录精简整合…

作者头像 李华
网站建设 2026/10/19 4:22:51

基于昇腾CANN与MindSpore的智慧家居安防系统全链路实战

最近我把家里的安防系统整个重写了一遍。不夸张地说&#xff0c;以前那套主要靠云平台做分析的方案&#xff0c;一到晚上就变成“误报发生器”——风吹窗帘也报警&#xff0c;猫从客厅跑过去也报警&#xff0c;半夜手机被震醒的感觉谁经历过谁知道。后来我决定换一条更贴近实战…

作者头像 李华
网站建设 2026/10/19 4:22:50

内核内存管理子系统构建全解析:从位图到页表

《操作系统真象还原》前面五章折腾完&#xff0c;你的内核已经能在屏幕上打印字符、能跳进保护模式、能开分页了。但你心里清楚&#xff0c;这只是个“花架子”——它没有任何自主管理资源的能力。第六章“完善内核”就是这道分水岭&#xff1a;这一章做完&#xff0c;内核才算…

作者头像 李华
网站建设 2026/10/19 4:22:47

open-code-review自动化代码评审实践:配置、规则与CI集成

1. 代码评审的现状与痛点做开发这些年&#xff0c;代码评审&#xff08;Code Review&#xff09;一直是个绕不开的话题。团队小的时候还好&#xff0c;几个人坐在一起&#xff0c;PR 提上来互相看一眼&#xff0c;口头说两句就过了。团队一扩张&#xff0c;问题就全出来了&…

作者头像 李华
网站建设 2026/10/19 4:22:35

FlareOn逆向入门:从PE动态调试到JavaScript混淆破译实战

先说个结论&#xff1a;如果你正在找逆向入门的练手题&#xff0c;[FlareOn2] very_success 和 [FlareOn3] Challenge1 是两堂绕不开的必修课。前者是一道标准的 Windows PE 动态调试题&#xff0c;考的是在调试器里观察程序行为、定位校验分支、修改内存状态让校验“按你的想法…

作者头像 李华