在工程开发里一个非常典型的需求:配置文件(XML、JSON、ini等)仅记录一个类名字符串,程序启动后读取这个名字,自动实例化出对应的C++对象。如果类型固定、数量很少,我们可以直接new或者一堆if-else / switch硬编码分支;但当业务持续迭代、对象类型源源不断新增,每次加类都要修改加载器的判断逻辑、重新编译核心加载代码,这套方案就会变得臃肿、脆弱、违背开闭原则。**注册式工厂(Registry Factory)**就是用来解决这个问题的经典方案,它的核心思想是提前把【类型名称】和【对象创建函数】绑定,存入一张全局注册表,运行时只需要根据配置读出的类名,查表拿到创建入口,统一调用创建逻辑。
我们可以循序渐进理解这套机制:从最简单的直接构造、分支判断,再引入模板简化重复代码,接着结合反射库RTTR实现通用注册,最后对接配置文件,形成一套稳定、可扩展的加载流程,下面全部基于C++17标准库演示。
目录
[五、串联完整链路:XML配置 → 名称查找 → 工厂/反射创建](#五、串联完整链路:XML配置 → 名称查找 → 工厂/反射创建)
一、起点:硬编码直接构造与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反射:把【类型信息、构造函数、属性】统一注册,实现跨体系的通用创建,是仿真框架里非常流行的落地方式。
注册式工厂的本质,就是将"类型名称"和"对象构造能力"的绑定关系,从编译期硬编码的分支里抽离,放到一张运行时注册表中。配置文件只负责传递名字字符串,加载器只负责查表调用,类型的扩展对加载逻辑透明。这也是很多仿真引擎、插件框架、脚本绑定底层最基础的设计模式。