现代 C++ 进化论:构建支持动态参数的自动注册工厂

在传统的 C++ 开发中,对象工厂模式往往伴随着臃肿的 switch-case 语句。每当新增一个派生类,开发者就不得不修改工厂源码。本文将带你利用 C++11 的高级特性——变长参数模板(Variadic Templates)、完美转发(Perfect Forwarding)与类型擦除(Type Erasure),构建一个既能自动注册,又支持运行时动态传参的顶级工厂模式。

在传统的 C++ 开发中,对象工厂模式往往伴随着臃肿的 switch-case 语句。每当新增一个派生类,开发者就不得不修改工厂源码。本文将带你利用 C++11 的高级特性——变长参数模板(Variadic Templates)、完美转发(Perfect Forwarding)与类型擦除(Type Erasure),构建一个既能自动注册,又支持运行时动态传参的顶级工厂模式。

一、 序言:从“腐烂”的简单工厂说起

传统的简单工厂模式通常是这样的:

1
2
3
4
5
6
7
8
Message* create(int type) {
switch (type) {
	case MSG_LOGIN: return new LoginMessage();
	case MSG_CHAT:  return new ChatMessage();
	// 随着业务增长这里将变成成百上千行
	default: return nullptr;
	}
}

这种模式有两个致命缺陷:

  • 维护噩梦:代码随业务无限膨胀。

  • 违背开闭原则(OCP):新增类必须修改原有代码,极其容易引入 Bug。

虽然此前我们通过“宏 + 静态辅助类”实现了自动注册,但那只能处理无参构造函数。在实际业务中,我们往往需要在创建实例时传入不同的参数(如用户 ID、等级、属性等)。

二、 核心技术栈:C++11 的三驾马车

要实现动态传参,我们需要解决:如何接收不定数量参数?如何保证参数不丢失属性地传递?如何存储签名完全不同的函数?

变长参数模板 (Variadic Templates):允许函数接收任意数量、任意类型的参数。

完美转发 (Perfect Forwarding):利用 std::forward 确保参数在多层传递中保持左值/右值属性,避免不必要的拷贝。

类型擦除 (Type Erasure):利用 void* 存储 Lambda 表达式生成的函数指针。

三、 架构设计:实现动态传参的黑魔法

我们的核心思路是:在注册时,不存储参数,而是存储一个支持特定签名的创建函数。

  1. 通用工厂模板类 我们定义一个泛型单例工厂,为每种基类族提供独立存储。
 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
template <typename Base>
class dynamic_factory {
public:
	static dynamic_factory& get() {
		static dynamic_factory instance;
		return instance;
	}

	// 注册接口Args 决定了构造函数的签名
	template <typename T, typename... Args>
	void register_class(const std::string& key) {
	    //  Lambda 转换为普通函数指针并存储为 void*
	    creators_[key] = reinterpret_cast<void*>(
	        +[](Args&&... args) -> Base* {
	            return new T(std::forward<Args>(args)...);
	        }
	    );
	}

	// 生产接口Args 在调用时动态推导
	template <typename... Args>
	std::unique_ptr<Base> produce(const std::string& key, Args&&... args) {
	    if (creators_.find(key) == creators_.end()) return nullptr;
	
	    // 强转回对应的函数指针签名
	    using FuncType = Base* (*)(Args&&...);
	    auto func = reinterpret_cast<FuncType>(creators_[key]);
	    return std::unique_ptr<Base>(func(std::forward<Args>(args)...));
	}

	private:
	std::map<std::string, void*> creators_;
	dynamic_factory() = default;
};
  1. 自动注册辅助类与宏 为了实现“一行代码注册”,我们需要升级此前的宏:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template <typename Base, typename T, typename... Args>
struct register_t {
	register_t(const std::string& key) {
		dynamic_factory<Base>::get().template register_class<T, Args...>(key);
	}
};

#define REGISTER_CLASS_VNAME(T) reg_gen_##T##_
#define REGISTER_CLASS(Base, T, key, ...) \
static register_t<Base, T, ##**VA_ARGS**> REGISTER_CLASS_VNAME(T)(key);

四、 实战演练:角色扮演游戏中的动态生产

假设我们在开发一款 RPG 游戏,拥有不同的角色类。

 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
// 定义角色族群
struct Character {
	virtual void info() = 0;
	virtual ~Character() {}
};

struct Warrior : Character {
	Warrior(std::string name, int level) : name_(name), level_(level) {}
	void info() override { std::cout << "战士: " << name_ << " 等级: " << level_ << "\n"; }
	std::string name_; int level_;
};
	
struct Mage : Character {
	Mage(std::string name, std::string element) : name_(name), element_(element) {}
	void info() override { std::cout << "法师: " << name_ << " 元素: " << element_ << "\n"; }
	std::string name_; std::string element_;
};

// 注册战士指定其构造参数为 (string, int)
REGISTER_CLASS(Character, Warrior, "warrior", std::string, int);

// 注册法师指定其构造参数为 (string, string)
REGISTER_CLASS(Character, Mage, "mage", std::string, std::string);

int main() {
	// 动态传入参数
	auto p1 = dynamic_factory<Character>::get().produce("warrior", std::string("亚瑟"), 30);
	auto p2 = dynamic_factory<Character>::get().produce("mage", std::string("甘道夫"), std::string("光辉"));
	p1->info(); // 输出: 战士: 亚瑟 等级: 30
	p2->info(); // 输出: 法师: 甘道夫 元素: 光辉
	
	return 0;
}

五、 避坑指南与总结

注意事项: 参数匹配的严格性:生产时传入的参数类型必须与注册时的 Args… 完全一致。例如注册了 std::string,生产时传入 const char* 可能会导致函数指针转换后的调用失败(除非通过显式构造转换)。

内存安全:生产接口返回 unique_ptr,强制用户接受所有权,避免了原始指针导致的内存泄漏。

结语

通过现代 C++ 的黑魔法,我们将工厂模式推向了极致。这套方案彻底消除了 switch-case,将对象创建的决策权交给了配置(Key)与数据(Args),同时保持了代码的优雅与开闭原则的纯粹。

Licensed under CC BY-NC-SA 4.0
最后更新于 2026-01-13 00:00