不用new而用工厂是为了降低耦合:工厂封装对象创建逻辑,上层只依赖接口;新增子类只需注册,无需修改业务代码;典型用于插件系统、策略选择和单元测试;需注意析构安全、查表异常及所有权管理。

为什么不用 new 直接创建对象,而要搞工厂?
因为硬编码 new Derived() 会让调用方和具体类型强耦合——换一个子类就得改所有 new 地方。工厂把“怎么造”收口到一处,上层只认接口,新增子类只需注册进工厂,不碰业务代码。
典型场景:插件系统、配置驱动的策略选择(比如日志后端选 FileLogger 还是 NetLogger)、单元测试中替换 mock 实现。
容易踩的坑:工厂返回裸指针却忘记定义析构逻辑,导致派生类析构函数不被调用;或者工厂内部用 std::map 查表但没处理键不存在的情况,直接 at() 崩溃。
最简静态工厂:用 if-else 还是 map + 函数指针?
小项目够用的写法就是 if-else,清晰、无依赖、编译期确定;大项目或需运行时动态注册时,才值得上 std::unordered_map<:string std::function>()>></:string>。
立即学习“C++免费学习笔记(深入)”;
关键差异在注册时机和生命周期管理:
- if-else 工厂:所有分支编译时固定,增删子类必须重编译
- map 工厂:支持运行时注册(比如 DLL 加载后调用
Register("net", []{ return std::make_unique<netlogger>(); });</netlogger>),但得自己管注册顺序和重复键 - 两者都建议返回
std::unique_ptr<base>,避免裸指针和内存泄漏
示例(map 版核心):
class LoggerFactory {
public:
static std::unique_ptr<Logger> Create(const std::string& type) {
auto it = creators_.find(type);
return it != creators_.end() ? it->second() : nullptr;
}
static void Register(const std::string& type, std::function<std::unique_ptr<Logger>()> creator) {
creators_[type] = std::move(creator);
}
private:
static std::unordered_map<std::string, std::function<std::unique_ptr<Logger>()>> creators_;
};
工厂里 new 出来的对象,谁负责 delete?
工厂本身不负责销毁——它只负责“生产并移交所有权”。返回 std::unique_ptr 就意味着调用方获得独占所有权,离开作用域自动析构;返回 std::shared_ptr 则由引用计数决定生命周期。
绝对不要返回裸指针(Base*)然后指望别人记得 delete,尤其当基类析构函数不是 virtual 时,delete 会漏掉派生类部分。
必须检查的点:
- 基类
Base的析构函数是否声明为virtual ~Base() = default; - 工厂函数签名是否明确体现所有权转移(如
std::unique_ptr<base>,而非Base*) - 如果用
std::shared_ptr,确认没有循环引用(比如对象内部又存了工厂的shared_ptr)
模板工厂能替代虚函数工厂吗?
可以,但适用面窄。模板工厂(比如 template<typename t> std::unique_ptr<base> Create()</typename>)在编译期绑定类型,零运行时开销,适合类型已知、无需运行时决策的场景(如泛型容器内部构造节点)。
但它无法解决“根据字符串名创建对象”这类需求——模板参数不能是运行时变量。这时候还是得靠虚函数+查表的动态工厂。
性能提示:模板工厂生成的代码可能膨胀,尤其是 T 多且每个都触发大量实例化时;动态工厂有查表和虚调用开销,但稳定可控。
一句话判断:需要配置文件/用户输入决定类型?→ 用动态工厂。只是想统一构造逻辑且类型编译期已知?→ 模板工厂更干净。
复杂点往往不在工厂结构本身,而在类型注册的线程安全、跨 DLL 边界的符号可见性、以及工厂单例的初始化顺序——这些比“怎么写 switch”容易让人卡住半天。










