不能直接用 std::make_unique 替代工厂类,因为它仅负责构造对象,无法实现运行时根据类型名或配置动态选择并创建具体派生类的逻辑;工厂需依赖注册机制(如 unordered_map + function)和启动前初始化来满足开闭原则与解耦要求。

为什么不能直接用 std::make_unique 替代工厂类
因为 std::make_unique 只解决“构造”,不解决“选择”。通用工厂的核心是运行时根据类型名、ID 或配置决定创建哪个派生类——这需要注册机制和类型映射,而标准库不提供。
常见错误是把工厂写成一堆 if-else 创建逻辑硬编码在调用处,导致每次新增子类都要改工厂代码,违反开闭原则。
- 真正需要的是:注册一次子类,后续只需传入标识符就能创建实例
- 注册必须在
main()之前完成(比如靠全局对象的构造函数) - 返回类型通常是基类指针(
std::unique_ptr<base>),不能是具体子类类型
std::unordered_map + 函数指针注册是最轻量的实现方式
不需要宏、不需要 RTTI、不依赖第三方,C++11 就能跑。关键是把“创建逻辑”抽象成一个可存储的值:
using Creator = std::function<:unique_ptr>()>; std::unordered_map<:string creator> registry;</:string></:unique_ptr>
每个子类注册时,只存一个无参 lambda:
registry["Circle"] = []() { return std::make_unique<circle>(); };
registry["Rect"] = []() { return std::make_unique<rect>(); };</rect></circle>
- 注册必须发生在任何
create()调用之前,推荐用局部静态变量或static全局对象触发 - 键名(如
"Circle")大小写敏感,建议统一用小写或加校验 - 如果子类构造需要参数,lambda 内部可捕获或从外部传入(见下一点)
带参数的创建怎么处理:别让工厂承担解析责任
工厂类本身不应解析 JSON、读配置文件或做字符串分割。它的接口应该保持简单:create(const std::string& type, const Args&... args) 或分两层设计。
- 推荐做法:工厂只接受已解析好的参数包,例如
std::any、std::variant<int double std::string></int>,或自定义的Config结构体 - 更稳妥的做法:工厂只负责无参构造,参数注入交给后续的
init()方法(要求所有子类实现统一接口) - 避免陷阱:不要在 lambda 里直接写
std::make_unique<t>(config.x, config.y)</t>—— 这会让注册代码依赖 config 定义,破坏解耦
线程安全不是默认选项,但容易被忽略
注册通常只在启动期发生,但 create() 可能在多线程高频调用。std::unordered_map 的并发读是安全的,但写(注册)和读不能同时发生。
- 最简方案:确保所有注册在
main()开始前完成,之后只读,无需锁 - 如果必须运行时注册(比如插件热加载),需用
std::shared_mutex(C++17)或std::mutex保护registry - 注意:
std::function对象拷贝有开销,高频场景下可考虑用函数指针替代(但失去捕获能力)
真正的难点不在语法,而在注册时机控制和参数传递边界的划分——这两点没想清楚,工厂会迅速变成新的耦合中心。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











