工厂函数应返回 std::unique_ptr而非裸指针,以自动管理内存、明确所有权转移;基类析构函数必须为virtual且protected;推荐注册表模式替代if-else分支,并注意线程安全与异常安全。

工厂函数返回 std::unique_ptr 而不是裸指针
直接用裸指针(new + Base*)实现工厂,等于把内存管理责任甩给调用方,极易导致泄漏或重复释放。C++11 后标准做法是让工厂函数返回 std::unique_ptr<base>——它自动管理生命周期,且语义明确:所有权转移,不可复制,只能移动。
常见错误现象:delete 忘记调用、多处 delete 导致崩溃、工厂返回栈对象地址(悬垂指针)。
- 工厂函数签名应为
std::unique_ptr<base> createProduct(const std::string& type),不是Base* - 内部用
std::make_unique<derived>()</derived>构造,避免裸new - 基类
Base的析构函数必须是virtual,否则unique_ptr释放时不会调用派生类析构函数
std::shared_ptr 仅在需要共享所有权时才用
如果多个模块需同时持有同一产品实例(比如缓存、观察者共用一个对象),才考虑 std::shared_ptr。但它的引用计数有开销,且容易因循环引用导致泄漏——这时裸指针或 std::weak_ptr 反而是更轻量的解法。
使用场景举例:一个图形系统中,渲染器和物理引擎都需访问同一个 GameObject 实例,且生命周期不完全一致。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 工厂返回
std::shared_ptr<base>时,仍推荐用std::make_shared<derived>()</derived>,而非shared_ptr<base>(new Derived) - 若下游只读、不延长生命周期,传参时优先用
const Base&或Base*,避免无谓增加引用计数 - 切勿在工厂内部用
shared_ptr管理局部对象(如shared_ptr<int>(&local_var)</int>),这是未定义行为
避免在工厂中硬编码 if-else 分支
当产品类型增多,if (type == "A") return make_unique<a>(); else if (type == "B")...</a> 很快变成维护噩梦,且无法在运行时动态注册新类型。
更健壮的做法是注册表模式:用 std::map<:string std::function>()>></:string> 存储构造器。
- 注册时机:全局变量、
static局部变量,或显式调用初始化函数(如registerProduct("A", []{ return std::make_unique<a>(); });</a>) - 查找失败时,抛出
std::runtime_error比返回空unique_ptr更易定位问题 - 注意线程安全:首次注册可加
std::call_once,查询时map本身读操作是线程安全的(C++11 起)
继承体系中基类必须有虚析构,且禁止公有析构
这是最容易被忽略的一环。如果 Base 的析构函数非 virtual,哪怕工厂返回的是 unique_ptr<base>,派生类的析构函数也不会被调用——资源泄漏、文件句柄未关闭、内存未释放全可能发生。
更进一步,应将 Base::~Base() 设为 protected,防止用户误用 Base b; 或 delete ptr_to_base;(后者在裸指针时代尤其危险)。
- 正确写法:
class Base { public: virtual ~Base() = default; protected: Base() = default; }; - 编译器会拒绝
delete一个指向Base的裸指针,但对unique_ptr<base>无影响——因为unique_ptr内部调用的是Base::~Base(),而它是public virtual - 若派生类有非 trivial 析构逻辑(如释放 C 风格资源),务必确保其析构函数也
noexcept,否则unique_ptr移动可能异常中止
unique_ptr 和注册表才是现代 C++ 的实际落点。真正难的从来不是怎么 new,而是怎么让 new 出来的东西,在没人盯着的时候,也能安全地死掉。

















