跳转到内容
新建笔记

C++ 对象工厂:静态创建、工厂方法与所有权

工厂把“选择具体类型并构造对象”的逻辑放在一个明确入口。调用方拿到统一接口后执行任务,不必在每个业务位置重复判断具体类型。

先分清两个问题:工厂解决在哪里创建对象;static 决定调用这个成员函数是否需要一个工厂实例。 二者没有必然绑定关系。

名称变化由谁决定典型实现
简单工厂集中的分支根据参数选择产品createTask("metric") 中使用 if 或注册表
工厂方法模式创建者子类决定具体产品基类声明虚创建函数,子类覆盖
抽象工厂一个工厂提供一组相互配套的产品同时创建某个平台的窗口、按钮等

日常交流中的“工厂函数”也可能只是一个返回对象的自由函数或静态函数。说到 GoF 的 Factory Method 模式时,则应明确由子类参与选择产品这一结构,不能仅凭函数名 create 或关键字 static 判断。Microsoft 的模式说明与 Schmidt 的课程定义区分了这些用法。

2. 为什么这个简单工厂可以写成 static

跳转到“2. 为什么这个简单工厂可以写成 static”

如果创建任务只需要传入的类型字符串,不依赖工厂对象中的配置、连接或服务,静态成员函数就足够:

auto task = TaskFactory::createTask("metric");

静态成员函数没有隐含的 this 指针,因此上述调用不用先构造 TaskFactory。它仍可使用显式传入的对象;“没有 this”不等于“整个函数不能访问任何对象”。依据:C++ 的 this 指针。

如果工厂需要持有设备配置或可替换的依赖,可以改成实例方法,并在构造工厂时注入这些依赖。若类仅起命名作用,也可以改用命名空间中的普通函数。选择标准是调用所需的状态和扩展方式,不是“工厂必须静态”。

3. 完整示例:创建、使用与失败处理

跳转到“3. 完整示例:创建、使用与失败处理”

下面代码使用 C++17。它保留原笔记中的 MetricTask、MillimeterTask 和未知类型分支,将手动 new/delete 改为明确的单一所有权。

#include <cassert>
#include <iostream>
#include <memory>
#include <string_view>
class Task {
public:
virtual ~Task() = default;
virtual void execute() const = 0;
};
class MetricTask final : public Task {
public:
void execute() const override {
std::cout << "Executing Metric Task\n";
}
};
class MillimeterTask final : public Task {
public:
void execute() const override {
std::cout << "Executing Millimeter Task\n";
}
};
class TaskFactory {
public:
static std::unique_ptr<Task> createTask(std::string_view type) {
if (type == "metric") {
return std::make_unique<MetricTask>();
}
if (type == "millimeter") {
return std::make_unique<MillimeterTask>();
}
return nullptr;
}
};
int main() {
auto metric = TaskFactory::createTask("metric");
auto millimeter = TaskFactory::createTask("millimeter");
auto unknown = TaskFactory::createTask("unknown");
assert(metric && millimeter && !unknown);
metric->execute();
millimeter->execute();
if (!unknown) {
std::cout << "Unknown task type\n";
}
}

预期输出:

Executing Metric Task
Executing Millimeter Task
Unknown task type

unique_ptr 返回后由调用者拥有对象;离开作用域时自动销毁。它不能复制,转移所有权需要移动。基类析构函数为 virtual,保证通过基类指针删除派生对象时调用正确的析构链。可对照 Microsoft 的 unique_ptr 示例。

这里约定“未知类型返回空指针”,因此使用前检查。实际接口也可以选择异常或显式错误结果,但必须让所有调用者遵守同一约定。make_unique 的内存分配失败仍可能抛出异常,不能把空指针分支理解为所有失败都被吞掉。

4. 真正需要工厂方法模式时

跳转到“4. 真正需要工厂方法模式时”

例如应用框架规定统一执行流程,却希望不同创建者决定创建哪种任务,可以在前述 Task 类型基础上定义:

class TaskRunner {
public:
virtual ~TaskRunner() = default;
void run() const {
auto task = createTask();
if (task) {
task->execute();
}
}
protected:
virtual std::unique_ptr<Task> createTask() const = 0;
};
class MetricRunner final : public TaskRunner {
protected:
std::unique_ptr<Task> createTask() const override {
return std::make_unique<MetricTask>();
}
};

TaskRunner::run() 依赖抽象产品;MetricRunner 决定产品类型。这是创建者继承体系中的变化点。它比几行分支增加了类型和间接调用,只在这种扩展方式确有价值时使用。

  • 新增任务是否只需要集中增加一个分支?是的话,简单工厂已经够用。
  • 多个调用方是否都在手动释放产品?优先用返回类型表达所有权。
  • 工厂是否偷偷读取全局配置?考虑显式参数或持有配置的工厂实例。
  • 字符串来自外部输入吗?需要定义大小写、空白和未知值处理;不要让错别字静默选择默认任务。
  • Qt 版本的接口可以接收 QString,但对象创建原则与 Qt 无关;普通 C++ 示例无需依赖 Qt。

本页合并两篇原始工厂记录,保留原例的两种产品、静态调用、未知类型处理及输出,同时更正模式术语并明确生命周期。