应使用工厂函数而非直接new,因其能集中管理对象创建逻辑、避免硬编码、便于扩展;需返回智能指针(推荐std::unique_ptr)、解耦构造逻辑、显式传参、线程安全且轻量,错误时抛具体异常。

为什么不用 new 直接创建,而要搞个工厂函数
因为业务逻辑里频繁出现 new DerivedA()、new DerivedB() 这类硬编码,一旦新增子类或切换实现,所有调用点都得改。工厂函数把“创建谁”这个决策收拢到一处,后续只改工厂内部,调用方完全无感。
常见错误现象:有人把工厂写成全局函数但返回裸指针,导致忘记 delete;或者用 std::shared_ptr 却在工厂里直接 new 而不配合 make_shared,白白多一次内存分配。
- 工厂函数应返回智能指针(推荐
std::unique_ptr<base>),避免资源泄漏 - 构造逻辑必须和产品类解耦——工厂函数里不能 #include 具体子类的 .h,只依赖基类声明
- 如果产品需要参数初始化,工厂函数参数应显式传递,别靠全局变量或单例塞配置
怎么写一个线程安全又不臃肿的简单工厂
简单工厂不是单例,不需要锁。所谓“线程安全”只在工厂内部有静态局部对象(比如缓存)时才需考虑;纯函数式工厂(输入类型标识,输出对象)天生线程安全。
性能影响:别在工厂里做耗时操作(如读配置文件、网络请求)。它应该只是 switch 或 if-else 分发,几十纳秒内完成。
立即学习“C++免费学习笔记(深入)”;
- 用
enum class ProductType代替 magic int/string,避免拼写错误和隐式转换 - 返回
std::unique_ptr<base>比std::shared_ptr更轻量,除非真需要共享所有权 - default 分支必须抛异常(如
std::invalid_argument),不能静默返回 nullptr——否则调用方容易忘判空
std::unique_ptr<Base> createProduct(ProductType type) {
switch (type) {
case ProductType::A: return std::make_unique<DerivedA>();
case ProductType::B: return std::make_unique<DerivedB>();
default: throw std::invalid_argument("Unknown product type");
}
}
工厂函数和 std::make_unique 混用要注意什么
std::make_unique 是安全的,但和工厂组合时容易掉坑:比如子类构造函数带参数,却忘了在工厂函数签名里暴露出来,或者参数类型没对齐导致编译失败。
使用场景:当不同产品需要不同初始化参数时,工厂函数得接受可变参数包,或拆成多个重载,而不是强行统一接口。
- 不要在工厂里写
new DerivedA(arg1, arg2),一定用std::make_unique<deriveda>(arg1, arg2)</deriveda> - 若参数是右值引用或移动语义敏感,确保工厂转发正确(用
std::forward) - 注意
std::make_unique不支持初始化列表构造(如{1,2,3}),这种得回退到new+ 手动管理,但应尽量避免
什么时候不该用简单工厂
当产品族变多(比如 WindowsButton / MacButton + WindowsDialog / MacDialog)、或创建逻辑本身变得复杂(依赖配置、状态、上下文),简单工厂会迅速膨胀成巨型 switch,违背单一职责。
兼容性影响:C++11 及以上才能用 std::make_unique;如果项目还在 C++03,工厂只能返回裸指针+约定调用方负责释放,风险陡增。
- 子类数量超过 5–6 个,就该考虑抽象工厂或注册表模式
- 需要延迟创建、池化复用、或按权重随机选型时,简单工厂已不够用
- 头文件依赖爆炸——如果工厂头文件被迫 include 所有具体子类头,说明设计边界错了
std::runtime_error。








