前言反射reflection指程序在运行时检查自身的类型、成员、函数并据此创建对象或调用方法。Java、C# 有完整的反射标准 C 至今没有。C 只有一套很有限的运行期类型信息run-time type information, RTTI能做类型识别和安全的向下转换但不能枚举一个类有哪些成员、不能按名字调用任意函数、不能根据字符串创建任意类型。常见的误解有三个。第一把typeid当成反射以为typeid(obj).name()能拿到可用的类名并据此创建对象——它返回的往往是编译器修饰过的名字且标准不保证内容。第二以为dynamic_cast能替代反射实际上它只能在已知目标类型的前提下做转换不能从字符串反推类型。第三以为 C26 的反射提案已经是可用标准实际上截至本文写作时它尚未成为已发布的正式标准各编译器的支持情况需要单独确认。工程上所谓的「C 反射」通常指手动注册 查表在程序启动时把「名字 → 创建函数」「名字 → 字段访问器」登记到一张表里运行时按名字查表。本文给出这套做法的完整示例并说明它的边界。一、RTTI 能提供什么RTTI 的两个主要工具是typeid和dynamic_cast。typeid作用于多态类型的左值时返回动态类型的std::type_info作用于非多态表达式时返回静态类型。type_info::name()返回的字符串由实现决定常见的是经过名称修饰name mangling的形式不能直接当类名用。type_info支持比较也可以用来做类型索引。// 适用于 C17#include iostream#include typeinfostruct Base {virtual ~Base() default;};struct Derived : Base {};int main() {Derived d;Base b d;std::cout typeid(b).name() \n; // 动态类型 Derivedstd::cout typeid(Base).name() \n; // 静态类型 Baseif (dynamic_castDerived*(b) ! nullptr)std::cout b really refers to Derived\n;}dynamic_castDerived*(p)在p为空时返回空指针转换失败也返回空指针。转换成引用形式dynamic_castDerived(r)失败时抛std::bad_cast。它要求源类型是多态类型至少有一个虚函数否则编译不通过。RTTI 可以被编译器选项关闭例如 GCC / Clang 的-fno-rtti关闭后typeid和dynamic_cast对多态类型不可用。一些性能敏感或嵌入式项目会这样做这也是「不能依赖 RTTI 做核心架构」的原因之一。二、注册表用字符串创建对象最常用的「反射」是工厂注册每个类在启动时把自己的名字和一个创建函数登记到全局表调用方只需提供名字。// 适用于 C17#include functional#include memory#include string#include unordered_mapclass Base {public:virtual ~Base() default;virtual std::string describe() const 0;};class Registry {public:using Factory std::functionstd::unique_ptrBase();static Registry instance() {static Registry registry;return registry;}void add(const std::string name, Factory factory) {factories_[name] std::move(factory);}std::unique_ptrBase create(const std::string name) const {const auto it factories_.find(name);if (it factories_.end()) return nullptr;return it-second();}private:std::unordered_mapstd::string, Factory factories_;};template class Tstruct Registrar {explicit Registrar(const std::string name) {Registry::instance().add(name, [] { return std::make_uniqueT(); });}};#define REGISTER_CLASS(T) \[[maybe_unused]] static RegistrarT g_reg_##T(#T)使用时在每个具体类的实现文件里写一行REGISTER_CLASS(Circle);。宏展开成一个静态对象它的构造函数在main之前执行把Circle和创建函数登记进去。#T把标识符转成字符串g_reg_##T用记号连接生成唯一变量名。// 适用于 C17class Circle : public Base {public:std::string describe() const override { return circle; }};REGISTER_CLASS(Circle);调用方// 适用于 C17std::unique_ptrBase make(const std::string name) {return Registry::instance().create(name);}这套机制有三个必须注意的工程点。第一静态对象的初始化顺序跨翻译单元未指定但注册只依赖Registry::instance()里的函数内静态对象首次调用时构造因此不受顺序影响。第二静态库链接器可能把「没有被任何符号引用」的目标文件整个丢掉注册代码也就不会执行需要显式引用、使用 whole-archive 链接选项或把注册放进一定会被链接的源文件。第三注册表在main之前构建多线程环境下如果运行期还要动态注册必须加锁。三、属性反射手动登记字段访问器比「按名字创建」更进一步的是「按名字读写字段」。标准 C 无法自动枚举成员只能为每个字段手工登记一个 getter 和一个 setter通常存成std::function。// 适用于 C17#include functional#include stdexcept#include string#include unordered_mapclass Object {public:virtual ~Object() default;};class Meta {public:using Getter std::functionstd::string(const Object);using Setter std::functionvoid(Object, const std::string);void add(const std::string name, Getter getter, Setter setter) {fields_[name] Field{std::move(getter), std::move(setter)};}std::string get(const Object obj, const std::string name) const {const auto it fields_.find(name);if (it fields_.end())throw std::out_of_range(unknown field: name);return it-second.getter(obj);}void set(Object obj, const std::string name, const std::string value) const {const auto it fields_.find(name);if (it fields_.end())throw std::out_of_range(unknown field: name);it-second.setter(obj, value);}private:struct Field {Getter getter;Setter setter;};std::unordered_mapstd::string, Field fields_;};为某个类登记字段时getter 和 setter 里必须把Object转回具体类型。这个转换的正确性由登记代码保证只有该类型的对象才会用这套字段表。如果把它做成「所有类型共用一张表」就必须在表里记录类型标识并在访问前校验否则static_cast到错误类型是 UB。属性表常见的用途是序列化、配置绑定、编辑器检视面板。它的优点是类型安全边界清晰每个字段的转换逻辑由写登记代码的人负责缺点是完全手动新增字段必须记得登记漏了不会编译报错只会在运行时表现为「字段不见了」。代码生成工具解析头文件或 IDL 生成注册代码能缓解这个问题但引入了构建步骤。四、边界与替代方案能力标准 C手动注册方案按名字创建对象不支持支持工厂表枚举成员不支持需手动登记按名字读写字段不支持支持访问器表按名字调用任意函数不支持需为每个函数写包装获取可读类名typeid().name()为实现定义注册时自己给名字跨语言/跨进程不支持需自定义协议如果项目能接受额外工具可以用 IDL、注解式宏或外部代码生成器来减少样板如果只是少量类型需要按名字创建手写注册表最直接。不要把注册表当成「完整反射」——它只覆盖你显式登记过的东西。常见坑点❌ 用typeid(obj).name()的结果当类名去查表或显示给用户。✅ 注册时自己指定稳定名字name()是实现定义的修饰名。❌ 把REGISTER_CLASS写在头文件里被多个源文件包含。✅ 放在具体类的实现文件中避免重复注册与无谓的目标文件膨胀。❌ 注册代码放在静态库里链接器没有拉取该目标文件注册未执行。✅ 显式引用该目标文件或使用 whole-archive 链接选项。❌ 认为dynamic_cast能按字符串创建对象。✅ 它只能在已知目标类型时做转换按名字创建需要工厂表。❌ 在运行期多线程动态注册却不加锁。✅ 注册集中在启动阶段或给注册表加互斥量。❌ 属性访问器里static_cast到错误的具体类型。✅ 每个类型独立字段表或访问前校验动态类型。❌ 以为 C26 反射已经可以直接使用。✅ 截至本文写作时标准尚未发布该能力按项目工具链实际情况确认。总结层面能做什么不能做什么RTTI识别动态类型、安全向下转换枚举成员、按名创建工厂注册表按名字创建对象自动发现未注册类型属性表按名字读写字段自动生成访问器代码生成减少手写登记需要额外构建步骤C 没有内建反射这不是缺陷而是「零开销」取向的取舍运行期类型信息只保留到能支持虚调用和安全转换的程度。工程上的反射都是显式注册出来的它的可靠性取决于登记是否完整。把注册集中在启动阶段、把名字与创建函数绑定、把字段访问器的类型转换封装在类型内部就能用很小的代价获得「够用」的反射能力。
企业数字化 ERP 产品动态
相关推荐
BFS与DFS算法对比:图论问题的最优选择策略 1. 算法选择的核心逻辑 在解决图论问题时,我们常常面临BFS(广度优先搜索)和DFS(深度优先搜索)的选择困境。这两种基础算法看似简单,但在实际应用中却有着截然不同的表现。我通过多年算法竞赛和工程实践发现… · 2026/11/4 7:46:37
分布式系统中智能体工作空间隔离实践与优化 1. 项目背景与核心需求 在分布式计算和多智能体系统开发中,工作空间隔离一直是个棘手问题。最近我在开发OpenClaw多智能体协作平台时,遇到了一个典型场景:当多个智能体同时操作同一目录下的文件时,频繁出现文件锁冲突和临时文件互… · 2026/11/4 7:46:37
Pytest Fixture循环限制解析与参数化测试实践 1. 问题背景与现象解析在pytest测试框架中,fixture是用于提供测试依赖的核心机制。最近社区中一个引发热议的现象是:当开发者尝试在fixture内部使用for循环来动态生成多个测试用例时,pytest会抛出明确禁止的异常。这个设计决策看似反直觉&… · 2026/11/4 7:45:37
从AI建站到企业经营:万小智如何打通生成与经营闭环 1. 从建站工具到经营系统的认知跃迁1.1 一个被大多数团队忽略的分水岭过去两年,我参与过十几个不同规模团队的数字化项目,从三五人的创业小队到上百人的传统企业转型部门。一个反复出现的现象是:大家把“AI建站”当成终点,上线一个… · 2026/11/4 8:30:02
模具制造业智能排产系统设计与OR-Tools实践 1. 项目背景与行业痛点模具制造业作为工业生产的"母机"行业,其生产排程复杂度常年位居离散制造业前列。我在广东某精密模具厂担任生产总监期间,每天需要处理300工序的排产任务,涉及EDM、CNC、线切割等20余种设备。传统手工排产需要… · 2026/11/4 8:30:02
Java基础:基本数据类型与 BigDecimal 基本数据类型类型大小byte1 个字节short2 个字节char2 个字节int4 个字节float4 个字节long8 个字节double8 个字节boolean无明确规定的存储字节大小BigDecimal
用于金额字段的对象。
在 Java 中进行浮点数运算时,可能会出现精度问题。例如,计算 0.05 0… · 2026/11/4 8:30:02
安卓开发入门:从零搭建环境到Hello World实战 1. 项目概述作为一名从零开始学习安卓开发的新手,我清楚地记得自己第一次搭建环境时的迷茫和完成第一个Hello World程序时的兴奋。这个教程将完整重现我的学习路径,用最接地气的方式带你完成第一个安卓应用开发。不同于官方文档的抽象说明,这… · 2026/11/4 8:30:02
容器化Maven构建实战:环境一致性、本地仓库复用与非root运行 1. 为什么要在容器里跑 Maven:从痛点说起如果你维护过超过三个 Java 项目,大概率遇到过这种场景:本地 Maven 版本是 3.8,CI 机器上是 3.6,同事的笔记本上又是 3.9,某次构建突然报出插件不兼容,排… · 2026/11/4 8:30:02
Cadence Virtuoso 入门实战:从反相器到版图全流程 简介:这份PDF教程面向初次接触集成电路版图设计的初学者,尤其适合需要完成课程设计或首次尝试流片的用户,帮助其快速上手Cadence Virtuoso这一常用IC设计工具。内容以最简单的反相器为例,完整走通从启动软件、管理Library/Cell/Vi… · 2026/11/4 8:29:01
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38