工厂模式的核心行为是将对象创建逻辑从调用方剥离,交由专门的“工厂”决定实例化哪个具体类;本质是通过虚函数+智能指针+多态实现运行时绑定,要求基类有虚析构函数,工厂返回std::unique_ptr等智能指针以明确所有权并避免内存泄漏。

什么是工厂模式的核心行为
工厂模式的本质是把对象创建逻辑从调用方剥离,交给一个专门的“工厂”来决定实例化哪个具体类。C++里没有内置工厂关键字,靠的是虚函数 + 指针/智能指针 + 多态组合实现。关键不是写个叫Factory的类,而是让create()返回基类指针,且运行时能绑定到子类构造。
常见错误是直接在工厂里用new Derived()硬编码返回裸指针,结果忘记释放、无法统一管理生命周期;或者把工厂做成全局函数却忽略线程安全。正确做法是配合std::unique_ptr或std::shared_ptr,并把工厂设计成可配置(比如传入类型标识符)。
用std::unique_ptr实现简单工厂(无注册表)
适用于产品类数量固定、不频繁增删的场景,比如解析器支持的几种协议类型(HttpParser、JsonParser)。
要点:
- 基类必须有虚析构函数,否则
unique_ptr<base>释放时不会调用子类析构 - 工厂函数返回
std::unique_ptr<base>,避免裸指针泄漏 - 用
enum class或std::string做参数比硬编码数字更安全
class Parser {
public:
virtual ~Parser() = default;
virtual void parse(const std::string& data) = 0;
};
<p>class JsonParser : public Parser {
public:
void parse(const std::string& data) override { /<em> ... </em>/ }
};</p><p>class XmlParser : public Parser {
public:
void parse(const std::string& data) override { /<em> ... </em>/ }
};</p><p>enum class ParserType { JSON, XML };</p><p>std::unique_ptr<parser> createParser(ParserType type) {
switch (type) {
case ParserType::JSON: return std::make_unique<jsonparser>();
case ParserType::XML: return std::make_unique<xmlparser>();
default: throw std::invalid_argument("unknown parser type");
}
}</xmlparser></jsonparser></parser></p>
用静态注册表实现工厂(支持运行时扩展)
适合插件式架构,比如日志后端可动态加载FileLogger、NetworkLogger,而主程序不依赖具体实现。
核心思路:每个子类在定义时自动向全局工厂注册自己的构造函数。利用静态变量初始化时机完成注册,避免手动调用register_*函数。
容易踩的坑:
- 注册函数必须是函数指针或
std::function,不能捕获局部变量 - 静态注册表本身需加
static或放在匿名命名空间,防止多定义 - 注册顺序不确定,不要依赖初始化顺序做逻辑判断
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& msg) = 0;
};
<p>using LoggerCreator = std::function<:unique_ptr>()>;</:unique_ptr></p><p>class LoggerFactory {
static std::unordered_map<:string loggercreator>& creators() {
static std::unordered_map<:string loggercreator> m;
return m;
}
public:
static void registerCreator(const std::string& name, LoggerCreator creator) {
creators()[name] = std::move(creator);
}
static std::unique_ptr<logger> create(const std::string& name) {
auto it = creators().find(name);
return it != creators().end() ? it->second() : nullptr;
}
};</logger></:string></:string></p><p>// 在 FileLogger.cpp 中
class FileLogger : public Logger {
public:
void log(const std::string& msg) override { /<em> ... </em>/ }
};
static bool file_logger_registered = []{
LoggerFactory::registerCreator("file", []{ return std::make_unique<filelogger>(); });
return true;
}();</filelogger></p>
为什么不用std::shared_ptr代替unique_ptr
除非明确需要共享所有权(比如多个模块同时持有同一对象),否则优先用std::unique_ptr。工厂返回shared_ptr会隐含“该对象可能被长期缓存或跨线程传递”的语义,但多数工厂场景下对象生命周期由使用者控制,unique_ptr更轻量、无引用计数开销、语义更清晰。
若真要用shared_ptr,注意两点:
- 注册表中存储的
std::function必须返回shared_ptr,不能混用 - 避免循环引用——比如工厂内部缓存了
shared_ptr又通过回调传回自身
工厂最难的部分不是写代码,而是界定“谁负责销毁”和“谁决定创建时机”。很多崩溃源于工厂返回了new出来的裸指针,而调用方忘了delete,或者误以为工厂会自动回收已创建对象。用智能指针不是炫技,是把所有权契约写进类型系统里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











