现代 C++(C++17/20)下的最佳工厂写法
一、现代 C++ 工厂设计的基本原则
在 C++17/20 下,好的工厂写法通常满足:
-
RAII + 明确所有权
- 返回
std::unique_ptr<T>为默认
- 返回
-
无
switch/ 无 RTTI -
支持扩展而不修改(OCP)
-
构造逻辑与业务逻辑分离
-
尽量零运行期成本或可控成本
二、最推荐写法:注册式工厂(Registry-based Factory)
90% 工程项目的首选
1. 基本结构
cpp
class Base {
public:
virtual ~Base() = default;
virtual void run() = 0;
};
using Creator = std::function<std::unique_ptr<Base>()>;
cpp
class Factory {
public:
static Factory& instance() {
static Factory f;
return f;
}
void registerCreator(std::string key, Creator creator) {
creators_.emplace(std::move(key), std::move(creator));
}
std::unique_ptr<Base> create(const std::string& key) const {
return creators_.at(key)();
}
private:
std::unordered_map<std::string, Creator> creators_;
};
2. 自动注册(推荐)
cpp
template<typename T>
struct AutoRegister {
AutoRegister(const std::string& key) {
Factory::instance().registerCreator(
key, [] { return std::make_unique<T>(); }
);
}
};
cpp
class ImplA : public Base {
public:
void run() override {}
};
static AutoRegister<ImplA> regA("A");
优点
- 零侵入
- 插件友好
- 新增类型无需改工厂
3. 为什么这是"现代"写法?
- 使用
unique_ptr明确所有权 - Lambda 消除样板代码
static局部变量保证线程安全(C++11+)- 完全 OCP
三、带参数的现代工厂写法(非常常见)
1. 问题
构造函数有参数:
cpp
ImplA(int w, double s);
2. 推荐方案:参数对象(Parameter Object)
cpp
struct Config {
int width;
double scale;
};
cpp
using Creator = std::function<std::unique_ptr<Base>(const Config&)>;
cpp
class Factory {
public:
void registerCreator(std::string key, Creator c) {
creators_[key] = std::move(c);
}
std::unique_ptr<Base> create(
const std::string& key, const Config& cfg) const {
return creators_.at(key)(cfg);
}
private:
std::unordered_map<std::string, Creator> creators_;
};
cpp
Factory::instance().registerCreator(
"A", [](const Config& cfg) {
return std::make_unique<ImplA>(cfg.width, cfg.scale);
});
工程价值
- 构造参数可扩展
- 工厂接口稳定
- 不破坏 ABI
四、C++20 改进:Concept + 工厂
适合大型工程 / 库级代码
cpp
template<typename T>
concept Product =
std::derived_from<T, Base> &&
std::default_initializable<T>;
cpp
template<Product T>
void registerType(std::string key) {
Factory::instance().registerCreator(
std::move(key), [] {
return std::make_unique<T>();
});
}
收益
- 编译期约束
- 错误更早暴露
- 接口自文档化
五、高性能版本:无 std::function
当工厂在热路径中被频繁调用:
1. 问题
std::function可能引入堆分配- 虚调用 + 间接跳转
2. 解决方案:函数指针 / 模板表
cpp
using Creator = std::unique_ptr<Base>(*)();
cpp
template<typename T>
std::unique_ptr<Base> createImpl() {
return std::make_unique<T>();
}
cpp
creators_["A"] = &createImpl<ImplA>;
权衡
- 性能更优
- 灵活性略降
- 适合内核模块
六、编译期工厂(零运行期开销)
当类型集合在编译期固定
cpp
template<typename T>
std::unique_ptr<Base> create() {
return std::make_unique<T>();
}
调用方:
cpp
auto obj = create<ImplA>();
适合
- 算法内核
- 无运行期配置
- 性能敏感路径
七、工厂 + 插件系统(dlopen)
典型结构:
text
Factory (主程序)
↑
PluginA.so → register("A")
PluginB.so → register("B")
插件加载时执行:
cpp
extern "C" void registerPlugin() {
Factory::instance().registerCreator(
"A", [] { return std::make_unique<ImplA>(); });
}
这是现代大型 C++ 系统最常见的用法之一。
八、现代 C++ 工厂的反例(请避免)
返回裸指针
cpp
Base* create();
工厂中塞业务逻辑
cpp
createAndRunAndValidate();
构造参数散落
cpp
create(int, int, double, bool);
九、快速选型总结
| 场景 | 推荐方案 |
|---|---|
| 配置驱动 | 注册式工厂 |
| 插件系统 | 自动注册 |
| 性能敏感 | 函数指针工厂 |
| 编译期固定 | 模板工厂 |
| API/SDK | Concept + Factory |
十、总结
现代 C++ 的工厂模式,本质是:
用类型系统 + RAII + 注册机制,
将"变化"限制在最小边界内。
一个真实 SLAM 模块的完整工厂实现
示例模块:FeatureExtractor
一、模块目标与设计边界
1. 目标
- 支持多种特征提取算法(ORB / FAST / SuperPoint)
- 通过配置文件选择实现
- 不修改核心逻辑即可新增算法
- 生命周期与 SLAM 系统一致
2. 架构位置(典型 SLAM)
text
Frontend
├── FeatureExtractor ← 工厂创建
├── FeatureMatcher
└── Tracker
FeatureExtractor 是典型"策略可替换模块"
二、接口定义
feature_extractor.h
cpp
#pragma once
#include <vector>
#include <opencv2/core.hpp>
struct Feature {
cv::KeyPoint keypoint;
cv::Mat descriptor;
};
class FeatureExtractor {
public:
virtual ~FeatureExtractor() = default;
virtual std::vector<Feature>
extract(const cv::Mat& image) = 0;
};
设计要点
- 纯接口
- 无构造参数
- 不暴露具体实现细节
三、参数对象(Config)
extractor_config.h
cpp
#pragma once
#include <string>
struct ExtractorConfig {
std::string type; // "ORB" / "FAST" / ...
int max_features = 1000;
float scale_factor = 1.2f;
int levels = 8;
};
关键工程点
- 构造参数集中
- 新增参数不破坏工厂接口
- 易于 YAML / JSON 映射
四、具体实现(变化层)
orb_extractor.h
cpp
#pragma once
#include "feature_extractor.h"
#include "extractor_config.h"
class ORBExtractor final : public FeatureExtractor {
public:
explicit ORBExtractor(const ExtractorConfig& cfg);
std::vector<Feature>
extract(const cv::Mat& image) override;
private:
int max_features_;
float scale_factor_;
int levels_;
};
orb_extractor.cpp
cpp
#include "orb_extractor.h"
ORBExtractor::ORBExtractor(const ExtractorConfig& cfg)
: max_features_(cfg.max_features),
scale_factor_(cfg.scale_factor),
levels_(cfg.levels) {}
std::vector<Feature>
ORBExtractor::extract(const cv::Mat& image) {
std::vector<Feature> features;
// ORB extraction logic...
return features;
}
五、工厂定义(核心)
feature_extractor_factory.h
cpp
#pragma once
#include <memory>
#include <unordered_map>
#include <functional>
#include <string>
#include "feature_extractor.h"
#include "extractor_config.h"
class FeatureExtractorFactory {
public:
using Creator =
std::function<std::unique_ptr<FeatureExtractor>(
const ExtractorConfig&)>;
static FeatureExtractorFactory& instance();
void registerCreator(const std::string& type, Creator creator);
std::unique_ptr<FeatureExtractor>
create(const ExtractorConfig& cfg) const;
private:
FeatureExtractorFactory() = default;
std::unordered_map<std::string, Creator> creators_;
};
feature_extractor_factory.cpp
cpp
#include "feature_extractor_factory.h"
#include <stdexcept>
FeatureExtractorFactory&
FeatureExtractorFactory::instance() {
static FeatureExtractorFactory factory;
return factory;
}
void FeatureExtractorFactory::registerCreator(
const std::string& type, Creator creator) {
creators_[type] = std::move(creator);
}
std::unique_ptr<FeatureExtractor>
FeatureExtractorFactory::create(const ExtractorConfig& cfg) const {
auto it = creators_.find(cfg.type);
if (it == creators_.end()) {
throw std::runtime_error(
"Unknown FeatureExtractor type: " + cfg.type);
}
return it->second(cfg);
}
六、自动注册(工程关键技巧)
extractor_register.h
cpp
#pragma once
#include "feature_extractor_factory.h"
template<typename T>
struct ExtractorRegistrar {
ExtractorRegistrar(const std::string& type) {
FeatureExtractorFactory::instance()
.registerCreator(
type,
[](const ExtractorConfig& cfg) {
return std::make_unique<T>(cfg);
});
}
};
orb_register.cpp
cpp
#include "orb_extractor.h"
#include "extractor_register.h"
static ExtractorRegistrar<ORBExtractor> reg_orb("ORB");
重要工程特性
-
无需修改工厂
-
新算法只需:
- 新类
- 新
.cpp注册文件
七、系统使用方式
cpp
ExtractorConfig cfg;
cfg.type = "ORB";
cfg.max_features = 1500;
auto extractor =
FeatureExtractorFactory::instance().create(cfg);
auto features = extractor->extract(image);
此处 Frontend 完全不知道 ORB 的存在
八、为什么这是"真实 SLAM 工厂实现"
1. 满足真实工程需求
- 算法频繁切换
- 配置驱动
- 生命周期清晰
- 可测试、可扩展
2. 与 SLAM 实际复杂度匹配
- 不引入 DI 框架
- 不过度模板化
- 不牺牲可读性
3. 易于扩展为插件系统
text
libslam_core.so
liborb_extractor.so
libsuperpoint_extractor.so
每个插件在加载时完成注册。
九、常见坑(真实踩坑总结)
1. 静态初始化顺序问题
问题: 注册对象在工厂之前初始化
方案: 使用 instance() 的局部静态
2. 工厂里塞逻辑
问题: 工厂决定参数
方案: 参数由 Config 决定
3. 返回裸指针
问题: 生命周期不明确
方案: std::unique_ptr
十、总结
在真实 SLAM 系统中,
工厂不是"设计模式练习",
而是"算法可演化性的基础设施"。