C++纯静态工厂

本文最后更新于:9 分钟前

技术背景

同一类设备往往有多个具体实现。例如,不同品牌的相机可以共用 Camera 接口,不同品牌的雷达可以共用 Radar 接口。调用者通过接口执行操作,但创建对象时仍需确定具体子类。如果每个使用位置都自行判断品牌并构造对象,类型选择和具体类名就会散落在各处,新增类型时也需要逐处调整。

工厂将这部分创建逻辑集中到统一入口:调用者提供类型标识,工厂返回相应的基类指针,后续操作沿用基类接口。本文中的“纯静态工厂”指注册和创建操作都通过静态成员函数提供,使用时直接调用 CameraFactory::create(brand),无需先创建工厂对象。C++ 的静态成员可以通过类名访问,静态成员函数没有 this 指针。C++ 标准草案:静态成员

统一入口内部也可以用 switchif 选择具体类型;本文采用注册表保存“类型标识 → 创建函数”的对应关系。注册时登记如何构造子类,创建时查表并执行相应函数,使工厂的查找逻辑与各子类的创建表达式分开。这里用 std::function 统一保存创建函数:它按给定调用签名封装可调用对象,因此不同子类的创建 lambda 可以存入同一种注册表。C++ 标准草案:std::function

基本思路

先封装产品接口类,由各类型子类继承接口类;再提供静态工厂。外部调用者通过工厂的 create() 获取产品指针,然后调用接口类中定义的函数。

工厂内部保存一张注册表,将产品类型映射到对应的创建函数。调用时只需要指定类型,不必在调用处直接写出具体子类。

下面从相机工厂开始,逐步介绍模板封装和不同的注册写法。

基础版:相机工厂

CameraFactory 使用 Camera::Brand 区分相机类型,创建函数统一返回 std::shared_ptr<Camera>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <functional>
#include "Camera.h"
#include "Logger.hpp"

class CameraFactory {
public:
using Creator = std::function<std::shared_ptr<Camera>()>;
static void registerCreator(Camera::Brand brand, Creator creator){
getRegistry()[brand] = creator;
};
static std::shared_ptr<Camera> create(Camera::Brand brand){
auto it = getRegistry().find(brand);
if (it != getRegistry().end()) {
return it->second();
}
SPD_ERROR("Camera Brand [{}] has not been registered! May be not included when compile!", static_cast<int>(brand));
return nullptr;
};
private:
static std::unordered_map<Camera::Brand, Creator>& getRegistry(){
static std::unordered_map<Camera::Brand, Creator> registry;
return registry;
};
};
// 相机子类注册宏 CameraType:相机子类类名, brand:相机基类中定义的品牌枚举值
#define REGISTER_CAMERA_FACTORY(CameraType, brand) \
namespace { \
struct Register##CameraType { \
Register##CameraType() { \
CameraFactory::registerCreator(Camera::Brand::brand, []() { \
return std::make_shared<CameraType>(); \
}); \
} \
}; \
static Register##CameraType _RegisterInstance##CameraType; \
} // namespace

这段代码分成三个部分:

  • registerCreator():把创建函数存入对应品牌的注册项。
  • create():查找品牌,找到后执行创建函数;未找到时输出日志并返回 nullptr
  • getRegistry():通过函数内的静态变量保存注册表。

REGISTER_CAMERA_FACTORY 把子类注册过程封装成宏。宏定义一个注册辅助类,在它的构造函数中调用 registerCreator(),再通过静态对象触发注册。传入的是创建相机的 lambda,实际创建相机的操作发生在调用该 lambda 时。

模板形式:复用工厂逻辑

将产品基类和类型键提取为模板参数,就可以把相同的注册、查找逻辑复用于其他产品。

  • T 表示产品基类。
  • U 表示区分产品类型的键,默认使用 std::string
  • Creator 表示返回 std::shared_ptr<T> 的创建函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <functional>
#include <iostream>
/**
* @brief 标准工厂类模板,纯静态工厂,无需实例化
* @tparam T 基类类名
* @tparam U 区分生成不同子类的类型值, 要求有默认哈希函数
*/
template <typename T, typename U = std::string>
class GeneralModelFactory {
public:
using Creator = std::function<std::shared_ptr<T>()>;
static void registerCreator(U type, Creator creator){
getRegistry()[type] = creator;
};
static std::shared_ptr<T> create(U type){
auto it = getRegistry().find(type);
if (it != getRegistry().end()) {
return it->second();
}
std::cerr << "Model Type [" << type << "] has not been registered!" << std::endl;
return nullptr;
};
private:
static std::unordered_map<U, Creator>& getRegistry(){
static std::unordered_map<U, Creator> registry;
return registry;
};
};
// 基类工厂简写宏,在基类hpp中声明。 BaseClassName:基类类名 TypeName:区分生成不同子类的类型值
#define BASEMODELCLASS_CREATE_FACTORY(BaseClassName, TypeName) \
using BaseClassName##Factory = GeneralModelFactory<BaseClassName, TypeName>

// 子类注册宏,在子类cpp中声明。 BaseClassName:基类类名 SubClassName:子类类名 TypeKey:具体类别(该值的类型是TypeName)
#define SUBMODELCLASS_REGISTER_FACTORY(BaseClassName, SubClassName, TypeKey) \
namespace { \
struct Register##SubClassName { \
Register##SubClassName() { \
BaseClassName##Factory::registerCreator(TypeKey, []() { \
return std::make_shared<SubClassName>(); \
}); \
} \
}; \
static Register##SubClassName _RegisterInstance##SubClassName; \
}

BASEMODELCLASS_CREATE_FACTORY 用于声明基类对应的工厂别名;SUBMODELCLASS_REGISTER_FACTORY 放在子类 .cpp 中,将类型键和子类创建函数关联起来。

在基类头文件中声明工厂

以雷达为例,使用 Radar::Brand 区分类型,在基类定义后声明工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public:
enum Brand {
UNKNOWN = 0,
BRAND_A = 1, // 示例品牌 A
BRAND_B = 2, // 示例品牌 B
BRAND_C = 3, // 示例品牌 C(串口)
};
public:
Brand brand_;
std::string ip;
int port{0};

public:
// 先按品牌构造,再设置ip等信息
Radar(Brand brand) : brand_(brand){};
void SetInfo(std::string ip, int port = 0)
{ // 仅设置有效值,防止覆盖默认参数
if (!ip.empty()) this->ip = ip;
if (port > 0) this->port = port;
};

virtual ~Radar() = default;
virtual int Open() = 0;
virtual int Close() = 0;
virtual int GetVersion(std::string &version) = 0;
virtual int GetNTP(NTPInfo &info) = 0;
virtual int SetNTP(NTPInfo info) = 0;
};

//创建雷达工厂,依靠Radar::Brand做区分
BASEMODELCLASS_CREATE_FACTORY(Radar, Radar::Brand);

在子类 cpp 中注册

在具体子类的实现文件中登记类型:

1
SUBMODELCLASS_REGISTER_FACTORY(Radar, RadarBrandB, Radar::Brand::BRAND_B);

这样,具体子类的注册放在自己的实现文件附近,调用者仍通过对应基类工厂获取产品指针。

在头文件中使用 inline 注册

在头文件中注册时,可以使用 C++17 的 inline 变量,通过初始化表达式中的 lambda 完成注册。

1
2
3
4
5
6
7
#define SUBCLASS_REGISTER_FACTORY_INLINE(BaseClassName, SubClassName, TypeName, type) \
inline bool _register_##SubClassName = [](){ \
GeneralModelFactory<BaseClassName, TypeName>::registerCreator(TypeName::type, [](){ \
return std::make_shared<SubClassName>(); \
}); \
return true; \
}();

这里省去了单独的注册辅助类,通过变量的初始化表达式调用 registerCreator()

同一个子类注册多个类型

前面的宏直接使用子类名生成注册辅助类和静态对象的名字。当同一个 .cpp 中需要为同一个子类注册多个类型时,可以把行号拼进这些名字,区分不同的注册位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// concat helpers
#define MODEL_CONCAT_IMPL(a, b) a##b
#define MODEL_CONCAT(a, b) MODEL_CONCAT_IMPL(a, b)
// use __LINE__ to make unique name inside same source file
#define SUBMODELCLASS_REGISTER_FACTORY(BaseClassName, SubClassName, TypeKey) \
namespace { \
struct MODEL_CONCAT(Register_, MODEL_CONCAT(SubClassName, __LINE__)) { \
MODEL_CONCAT(Register_, MODEL_CONCAT(SubClassName, __LINE__))() { \
BaseClassName##Factory::registerCreator( \
TypeKey, []() { return std::make_shared<SubClassName>(); } \
); \
} \
}; \
static MODEL_CONCAT(Register_, MODEL_CONCAT(SubClassName, __LINE__)) \
MODEL_CONCAT(_RegisterInstance_, MODEL_CONCAT(SubClassName, __LINE__)); \
}

这一版本沿用前面的注册过程,通过不同的名字区分注册辅助类和静态对象。MODEL_CONCAT_IMPL 负责拼接,外层 MODEL_CONCAT 配合 __LINE__ 生成各注册位置的名字。