在工程开发里一个非常典型的需求:配置文件(XML、JSON、ini等)仅记录一个类名字符串,程序启动后读取这个名字,自动实例化出对应的C++对象。如果类型固定、数量很少,我们可以直接new或者一堆if-else / switch硬编码分支;但当业务持续迭代、对象类型源源不断新增,每次加类都要修改加载器的判断逻辑、重新编译核心加载代码,这套方案就会变得臃肿、脆弱、违背开闭原则。**注册式工厂(Registry Factory)**就是用来解决这个问题的经典方案,它的核心思想是提前把【类型名称】和【对象创建函数】绑定,存入一张全局注册表,运行时只需要根据配置读出的类名,查表拿到创建入口,统一调用创建逻辑。
我们可以循序渐进理解这套机制:从最简单的直接构造、分支判断,再引入模板简化重复代码,接着结合反射库RTTR实现通用注册,最后对接配置文件,形成一套稳定、可扩展的加载流程,下面全部基于C++17标准库演示。
目录
一、起点:硬编码直接构造与if-else分支(反例)
二、第一步改进:把创建行为抽象成可存储的函数
三、模板封装:自动生成创建函数(纯标准库工厂)
四、引入RTTR:基于运行时反射的通用对象创建
五、串联完整链路:XML配置 → 名称查找 → 工厂/反射创建
六、设计思想总结
补充:注册式工厂与 RTTR(Run-Time Type Reflection)
一、起点:硬编码直接构造与if-else分支(反例)
假设我们有两个基础组件类CameraSensor、RadarSensor,都继承同一个基类BaseSensor,加载器读取配置字符串后创建对象。
#include <string> #include <memory> struct BaseSensor { virtual ~BaseSensor() = default; virtual void run() = 0; }; struct CameraSensor : BaseSensor { void run() override; }; struct RadarSensor : BaseSensor { void run() override; }; // 最朴素写法:分支判断 std::unique_ptr<BaseSensor> create_by_name(const std::string& cls_name) { if (cls_name == "CameraSensor") { return std::make_unique<CameraSensor>(); } else if (cls_name == "RadarSensor") { return std::make_unique<RadarSensor>(); } return nullptr; }这套代码逻辑直白,但缺陷非常明显:每新增一个Sensor子类,必须修改这个if-else列表。加载器代码持续膨胀,类型和创建逻辑耦合在一起,不符合“对扩展开放、对修改关闭”。当类型上百个、分散在不同模块,维护成本会急剧上升,这就是我们要改造的起点。
二、第一步改进:把创建行为抽象成可存储的函数
C++17的std::function可以保存无状态的创建回调。我们把“创建对象”这件事封装成一个函数签名,注册表本质就是一个std::unordered_map<std::string, CreatorFunc>,key是配置里的类名字符串,value是创建函数。
#include <functional> #include <unordered_map> using CreatorFunc = std::unique_ptr<BaseSensor>(); std::unordered_map<std::string, std::function<CreatorFunc>> g_creator_registry; // 注册接口:名字 + 创建函数 void register_class(const std::string& name, std::function<CreatorFunc> fn) { g_creator_registry[name] = std::move(fn); } // 统一加载入口,不再写if-else std::unique_ptr<BaseSensor> factory_create(const std::string& cls_name) { auto it = g_creator_registry.find(cls_name); if (it == g_creator_registry.end()) return nullptr; return it->second(); }现在加载器factory_create逻辑永久固定,不再随类型增加而改动。新增类的时候,只需要调用register_class把名字和构造函数注册进去。
// 注册示例 register_class("CameraSensor", [](){ return std::make_unique<CameraSensor>(); }); register_class("RadarSensor", [](){ return std::make_unique<RadarSensor>(); });但这里还有冗余:每注册一个类,都要手写lambda包装make_unique,重复样板代码多,接下来用模板消除重复。
三、模板封装:自动生成创建函数(纯标准库工厂)
利用C++17类模板,自动生成对应类型的创建闭包,不用手写lambda:
template<class T> std::unique_ptr<BaseSensor> type_creator() { return std::make_unique<T>(); } // 简化注册调用 register_class("CameraSensor", type_creator<CameraSensor>); register_class("RadarSensor", type_creator<RadarSensor>);更进一步,封装一个注册宏(工程中很常见),一行完成注册:
#define REGISTER_CLASS(Cls) register_class(#Cls, type_creator<Cls>) // 使用:REGISTER_CLASS(CameraSensor);到这里,我们已经实现了纯C++17标准库的注册式工厂。 它的边界是:注册动作必须在程序启动阶段显式调用;类型名是字符串硬绑定;工厂是业务自己手写的、只适配BaseSensor这一套继承树。如果项目里有多套不同基类的对象体系,就要写多套工厂、多张注册表,通用性不足。这就是反射框架RTTR登场的场景。
四、引入RTTR:基于运行时反射的通用对象创建
RTTR是一个轻量C++反射库,它的能力是:在运行期,通过类型名字符串拿到类型元信息、调用构造函数、遍历成员,不需要我们手写每一套工厂的创建回调。
它和上面手写工厂不是替代关系:手写工厂是【注册创建函数】,RTTR是【注册类型元信息,从元信息直接构造实例】,本质都属于名称到类型的映射,只是映射载体从自定义CreatorFunc变成了反射类型对象
rttr::type。
RTTR基础注册与对象创建示例(C++17):
#include <rttr/registration> using namespace rttr; RTTR_REGISTRATION { registration::class_<CameraSensor>("CameraSensor") .constructor<>(); // 注册无参构造函数 registration::class_<RadarSensor>("RadarSensor") .constructor<>(); } // 通用加载函数:输入类名字符串,返回基类指针 std::unique_ptr<BaseSensor> rttr_create(const std::string& cls_name) { auto t = type::get_by_name(cls_name); if (!t.is_valid()) return nullptr; variant obj = t.create(); if (!obj.is_valid()) return nullptr; return obj.get_value<std::unique_ptr<BaseSensor>>(); }对比前面手写的标准库工厂,RTTR的优势:
注册表由RTTR内部维护,不需要我们自己维护
unordered_map;一套反射注册机制,支持多套继承体系、不同基类;
除了创建对象,还附带运行时属性读写、方法调用,非常适合配置驱动的仿真框架;
类名直接在RTTR注册阶段绑定,和配置文件的类名一一对应。
在仿真框架场景下,这个流程非常契合:仿真引擎读取XML配置节点,节点属性包含class_name="CameraSensor",引擎把字符串传给rttr_create,得到仿真组件对象,加入仿真世界。后续新增仿真组件,只需要增加RTTR注册代码,核心仿真加载器一行不用改,完美满足“类型持续增加,加载流程不变”的需求。
五、串联完整链路:XML配置 → 名称查找 → 工厂/反射创建
完整数据流是:
程序启动阶段:所有组件模块执行注册(自定义工厂注册Creator 或 RTTR注册type+构造);
加载器读取XML配置,解析出
<Sensor class="CameraSensor"/>中的类名字符串;将字符串传入统一创建接口;
查表(自定义map / RTTR内部类型表)找到对应的创建入口;
调用构造,返回基类指针,交由仿真框架管理、执行仿真逻辑。
这里需要区分两种选型的取舍:
纯C++17标准库注册工厂:无第三方依赖、编译快、可控性极强,适合性能敏感、不想引入反射库的模块;代价是需要自己维护注册表、创建函数,跨基类复用麻烦。
RTTR反射方案:代码更简洁,自带元信息,适合仿真、编辑器、配置驱动这类需要大量运行时类型信息的场景;代价是引入第三方库,有少量反射运行时开销。
六、设计思想总结
整个演进路径,是从“固定分支写死创建”到“注册式动态分发”的典型工程范式:
直接构造:最简单,完全不支持动态名称;
if-else分支:可以按名字创建,但新增类型要修改加载核心,违反开闭原则;
std::function + 注册表:把创建能力剥离出来,加载逻辑稳定,新增类只需要注册;
模板简化注册:消除重复样板代码,是标准库实现工厂的最优形态;
RTTR反射:把【类型信息、构造函数、属性】统一注册,实现跨体系的通用创建,是仿真框架里非常流行的落地方式。
注册式工厂的本质,就是将“类型名称”和“对象构造能力”的绑定关系,从编译期硬编码的分支里抽离,放到一张运行时注册表中。配置文件只负责传递名字字符串,加载器只负责查表调用,类型的扩展对加载逻辑透明。这也是很多仿真引擎、插件框架、脚本绑定底层最基础的设计模式。
补充:注册式工厂与 RTTR(Run-Time Type Reflection)
注册式工厂是一套设计模式(思想);RTTR 是一个 C++ 反射库(工具 / 基础设施),二者不是对等的 “两种互斥实现方案”,是「设计模式」和「用来落地这个模式的库」的关系。
1. 分开理解两个核心概念
① 注册式工厂(Register Factory)
它是架构设计模式 / 核心思想,是实现配置驱动动态创建对象的核心方案,具备四大核心要素:
全局注册表:维护
字符串类名 → 对象构造器(工厂函数/可调用对象)的映射关系,是动态创建的核心载体;统一注册接口:提供通用注册方法(如
register_class),在模块初始化阶段,将自定义类名与对应对象构造函数绑定并存入注册表;固定加载流程:程序运行时读取配置文件中的类名字符串,通过注册表查表匹配,调用已注册的构造器生成对象实例;
高扩展特性:新增业务类时,仅需编写对应注册代码,无需修改、编译加载器核心主逻辑,新增类型对核心加载流程完全透明。
✅ 完全可以不用任何反射库,仅依托 C++17 原生特性(std::function、std::unordered_map、模板、静态初始化)独立实现完整的注册式工厂。 该手写方案本质是手工反射 + 注册工厂,通过代码手动维护类名、构造函数、对象属性的映射关系,实现基础的动态创建能力。
② RTTR(Run-Time Type Reflection)
RTTR 是一款成熟的第三方 C++ 运行时反射库,是落地注册式工厂的工具与基础设施,自身不代表工厂模式,仅提供底层反射能力:
支持运行时获取、管理类型元信息
rttr::type;可注册类的构造函数、成员变量(属性)、成员方法;
支持通过类名字符串反向查询类型、调用构造器、读写对象属性;
内置完善的全局类型注册表、元信息存储与管理机制。
RTTR 本身不等于注册工厂,但它天然具备名字 → 类型 → 构造器的完整查找与实例化能力。 在仿真框架、配置驱动业务框架中的主流落地方式:
复用 RTTR 原生的类型注册表、反射构造、属性解析能力,在此基础上封装一层业务专属工厂逻辑,对外提供统一的 “按配置名称创建业务对象” 接口,大幅简化开发工作量。
2. 两种实现路径完整对比
结合 C++17 原生特性与 RTTR 框架,注册式工厂分为两种落地路径,对应全文的两套实现逻辑:
路径 A:纯 C++17 手写注册式工厂(无 RTTR 依赖)
注册表实现:基于
std::unordered_map<std::string, std::function<void*()>>手动构建映射表;注册方式:通过模板、宏定义封装通用注册逻辑,将类名与匿名构造函数自动注册到 Map;
对象创建:加载器解析配置字符串,遍历 Map 匹配对应工厂函数,调用生成对象实例;
核心短板:需开发者手动维护所有类型元信息,属性绑定、构造函数重载、XML/JSON 序列化适配、异常校验等逻辑均需手写,反射能力简陋且高度定制化;
本质定义:原生注册式工厂,手动实现反射绑定逻辑。
路径 B:基于 RTTR 实现注册式工厂(本文核心方案)
注册表实现:复用 RTTR 底层内置的全局类型注册表,无需手动创建 Map 结构;
注册方式:封装通用注册模板,内部调用 RTTR 原生注册接口,完成类、构造函数、属性的自动注册,同时绑定业务层创建回调;
对象创建:加载器读取配置字符串 → 调用 RTTR 接口查询对应类型元信息 → 匹配构造器生成实例,可直接依托 RTTR 反射能力解析 XML 配置、读写对象属性;
核心优势:自带完善的运行时反射能力,支持批量属性绑定、动态方法调用、配置自动映射,适配复杂仿真、大型配置驱动框架;
本质定义:标准注册式工厂,复用 RTTR 通用反射基础设施实现增强落地。
3. 核心认知误区澄清
❌ 错误认知:纯 C++ 手写工厂、RTTR 工厂是两种互斥、二选一的替代方案;
✅ 正确认知:
- 注册式工厂是固定的设计目标、核心架构流程(动态注册、查表创建、无需改核心代码);
- RTTR 是简化落地的工具,用于替代繁琐的手动反射与注册表维护逻辑;
- 技术选型灵活:轻量化场景可不用 RTTR、纯原生实现;复杂框架场景可依托 RTTR 快速实现标准化注册工厂。
- RTTR 原生仅提供基础反射构造能力,并非面向业务的工程化工厂。实际开发中,需在 RTTR 上层封装业务适配层,实现基类统一转换、创建前后钩子函数、业务参数校验、仿真域管理等能力,这层业务封装,就是工程中真正落地的注册式工厂。
4. 标题核心含义一句话总结
「注册式对象工厂」是本文实现的核心架构设计与业务目标;「基于 RTTR」是指该工厂的类型注册、名称查找、对象构造、属性反射等底层能力,全部依托 RTTR 反射库实现,区别于纯 C++17 手动维护注册表的轻量化原生方案。
补充:反射类型注册 + 动态对象创建链路
(1)启动 / 注册阶段(编译期模板实例化);
ev::EvMetaType::register_class<ev::EvObject>("ev::EvObject");(2)编译器生成Class_Type=MyActor的函数版本;
template<typename Class_Type> static rttr::registration::class_<Class_Type>& register_class(std::string aName){ // 静态容器:保存 rttr 注册对象,防止它被析构(RTTR 注册对象销毁会注销类型) static std::vector<std::unique_ptr<rttr::registration::class_<Class_Type>>> clsStorage; // 新建一个 RTTR 的 class_ 注册器,类型名 aName std::unique_ptr<rttr::registration::class_<Class_Type>> cls = std::make_unique<rttr::registration::class_<Class_Type>>(aName); // ✅【重点:在这里绑定工厂】 EvMetaType::instance().registerClassCreator( rttr::type::get<Class_Type>(), std::bind(&EvCreateClassT<Class_Type>, std::placeholders::_1) ); // 移入容器托管生命周期 clsStorage.push_back(std::move(cls)); // 返回 class_ 引用,支持链式注册(.property()/.method()) return *clsStorage.back(); }(3)在函数内部:bind EvCreateClassT<MyActor>,把这个工厂注册到EvMetaType;
凡是专门负责「创建对象实例」、把对象构造逻辑封装起来,对外只暴露一个调用入口的函数 / 类,都叫工厂(Factory);EvCreateClassT<Class_Type>符合这个定义,所以叫工厂函数。
template<typename T> rttr::variant EvCreateClassT(EvCreateClassMode aMode){ switch (aMode) { // 对象 case EvCreateClassMode::CCM_OBJECT: return T{}; // 裸指针 case EvCreateClassMode::CCM_RAW_PTR: return new T{}; // 共享智能指针 case EvCreateClassMode::CCM_STD_SHARED_PTR: return std::make_shared<T>(); default: return {}; } }(4)EvMetaType内部存一张映射表:rttr::type → 创建器函数;
(5)运行时(编辑器 / 序列化)
- 拿到类型字符串
"ev::EvObject",查 RTTR 得到rttr::type; - 拿着这个 type 去
EvMetaType查表,取出之前 bind 好的创建器; - 传入
EvCreateClassMode(比如 CCM_STD_SHARED_PTR)调用; - 内部就跑到
EvCreateClassT<EvObject>的 switch,返回rttr::variant(shared_ptr<EvObject>)。