返回 std::unique_ptr 最稳妥:自动管理生命周期、明确移交所有权;裸指针易泄漏,引用易悬垂;基类析构函数必须 virtual,否则子类资源泄漏;运行时工厂需 map/constexpr 注册,模板工厂不适用动态类型场景。

工厂函数返回指针还是引用更安全
直接返回 std::unique_ptr 是当前最稳妥的选择。裸指针容易引发内存泄漏,引用又无法表达“可能为空”或转移所有权的语义。
- 返回
std::unique_ptr<base>:自动管理生命周期,调用方无需关心 delete,且能明确表示对象由工厂创建并移交所有权 - 避免返回
Base*:一旦忘记delete或重复delete,就是未定义行为;RAII 被绕过了 - 不返回
Base&:工厂内部局部对象返回引用是悬垂引用;堆上 new 出来再引用,又得手动管理,和裸指针一样危险 - 如果必须兼容旧代码且确定调用方会严格配对 delete,才考虑返回
Base*,但要加注释警告
如何避免 if-else 分支膨胀(C++17 及以后)
硬写一长串 if (type == "A") { return std::make_unique<a>(); }</a> 很快就不可维护,尤其新增类型时容易漏改。
- 用
std::map<:string std::function>()>></:string>预注册构造逻辑,查找 + 调用即可 - 利用
constexpr+ 类型列表(如std::tuple)在编译期注册,避免运行时 map 查找开销(适合类型固定、数量少的场景) - 注意:字符串比较(如
"A")大小写敏感,建议统一转小写或用枚举代替字符串键,防止拼写错误导致返回空指针 - 注册时没做重复检查?
map.insert()默认不覆盖,但不会报错——查不到类型时返回空unique_ptr,容易静默失败
基类析构函数没设 virtual 会怎样
这是工厂模式下最常踩的坑:对象能创建,也能调用虚函数,但 delete 时只调用基类析构,子类资源(文件句柄、动态内存等)彻底泄漏。
- 只要基类有虚函数(比如
virtual void execute() = 0;),就顺手把析构函数也声明为virtual ~Base() = default; - 不加
virtual,哪怕子类析构里写了清理逻辑,也不会执行——C++ 标准规定,通过基类指针 delete 派生类对象时,基类析构非 virtual 就是未定义行为 - Clang/GCC 开启
-Wnon-virtual-dtor可捕获这类问题;但该警告只触发于“有虚函数但析构非 virtual”,没有虚函数的基类不会报,所以别依赖警告
为什么不用模板工厂替代运行时工厂
模板工厂(比如 create<t>()</t>)编译期确定类型,零成本抽象,但它解决的是不同问题:它不处理“运行时才知道要哪种对象”的场景。
立即学习“C++免费学习笔记(深入)”;
- 配置文件读出 type 字符串、用户输入选择功能模块、插件动态加载——这些都只能在运行时决定类型,模板无法介入
- 模板工厂导出符号困难:每个实例化版本生成独立函数,若需 DLL/so 导出,得显式实例化所有可能类型,维护成本高
- 如果只是想避免字符串映射开销,且类型集合完全固定,可结合
std::variant+ 访问者模式,但依然要运行时 dispatch,不是纯模板方案
真正难的从来不是怎么写一个工厂,而是怎么让工厂的类型注册、错误提示、线程安全(多线程调用时 map 初始化是否安全)、以及和 DI 容器的衔接不变成新包袱。










