哈喽,各位好!今天咱们来聊聊一个C++里挺高级也挺酷的东西:编译期工厂模式,而且还是基于类型列表的。这东西听起来可能有点吓人,但其实只要你把它拆解开来,就会发现它并没有那么神秘,反而能让你在编译期玩出很多花样。
啥是工厂模式?为啥要编译期?
首先,咱们先简单回顾一下工厂模式。简单来说,工厂模式就是把对象的创建过程给封装起来。你不用关心对象是怎么被new出来的,只要告诉工厂你想要啥,工厂就会帮你把东西搞定。这样一来,你的代码就变得更灵活、更易于维护。
传统的工厂模式通常是在运行时工作的,也就是程序跑起来的时候才决定创建哪个对象。但有时候,我们希望能在编译的时候就把这些事情确定下来。这样做的好处是:
- 性能更高: 编译期完成的事情,运行时就不用做了,可以省下不少时间。
- 类型安全: 编译期就能检查类型错误,避免运行时出现一些莫名其妙的问题。
- 更灵活: 可以根据编译时的条件,选择不同的对象创建方式。
类型列表:编译期工厂的基石
要实现编译期工厂,类型列表(Type List)是必不可少的工具。类型列表本质上就是一个包含了若干类型的列表,但这个列表是在编译期就确定的。C++11之后,我们可以用模板元编程来实现类型列表。
一个简单的类型列表可能是这样的:
template<typename... Types>
struct TypeList {};
using MyTypeList = TypeList<int, double, std::string>;
这里,TypeList
就是一个模板类,可以接受任意数量的类型作为模板参数。MyTypeList
就是一个具体的类型列表,包含了int
、double
和std::string
这三种类型。
编译期工厂的核心:模板元编程
有了类型列表,我们就可以开始构建编译期工厂的核心逻辑了。这部分主要依赖于模板元编程。模板元编程是一种在编译期进行计算的技术,它可以让我们在编译期生成代码、进行类型推导等等。
为了方便,我们先定义一个简单的基类和一个对象创建接口:
// 所有产品类的基类
class BaseProduct {
public:
virtual ~BaseProduct() = default;
virtual void doSomething() = 0;
};
// 创建对象的接口
template<typename T>
BaseProduct* createObject() {
return new T();
}
接下来,我们要实现一个编译期工厂类,它能根据类型列表中的类型,选择合适的创建函数来创建对象。
template<typename ProductType, typename... Types>
class Factory; // 前置声明
// 特化版本,处理空类型列表
template<typename ProductType>
class Factory<ProductType> {
public:
static ProductType* create(const std::string& typeName) {
throw std::runtime_error("Unknown type: " + typeName);
}
};
// 通用版本,递归处理类型列表
template<typename ProductType, typename T, typename... Types>
class Factory<ProductType, T, Types...> {
public:
static ProductType* create(const std::string& typeName) {
if (typeName == typeid(T).name()) {
return createObject<T>(); // 使用 createObject 创建 T 类型的对象
} else {
return Factory<ProductType, Types...>::create(typeName); // 递归调用
}
}
};
这个Factory
模板类有两个版本:
- 特化版本: 当类型列表为空时,会抛出一个异常,表示找不到对应的类型。
- 通用版本: 递归地处理类型列表。如果
typeName
和当前类型T
的类型名匹配,就调用createObject<T>()
来创建对象;否则,就递归调用Factory
模板类,处理剩余的类型列表。
类型名获取的坑:typeid
和编译器
上面代码里用到了typeid(T).name()
来获取类型名。但是,typeid
获取的类型名在不同的编译器下可能是不一样的,这会导致我们的工厂在不同的平台上表现不一致。
为了解决这个问题,我们可以使用一些宏来定义类型名,保证在不同的编译器下都能得到相同的结果。例如:
#define PRODUCT_TYPE_NAME(T) #T
class ConcreteProductA : public BaseProduct {
public:
void doSomething() override { std::cout << "Product A" << std::endl; }
static constexpr const char* typeName = PRODUCT_TYPE_NAME(ConcreteProductA);
};
class ConcreteProductB : public BaseProduct {
public:
void doSomething() override { std::cout << "Product B" << std::endl; }
static constexpr const char* typeName = PRODUCT_TYPE_NAME(ConcreteProductB);
};
然后,把Factory
类改成这样:
template<typename ProductType, typename... Types>
class Factory; // 前置声明
// 特化版本,处理空类型列表
template<typename ProductType>
class Factory<ProductType> {
public:
static ProductType* create(const std::string& typeName) {
throw std::runtime_error("Unknown type: " + typeName);
}
};
// 通用版本,递归处理类型列表
template<typename ProductType, typename T, typename... Types>
class Factory<ProductType, T, Types...> {
public:
static ProductType* create(const std::string& typeName) {
if (typeName == T::typeName) {
return createObject<T>(); // 使用 createObject 创建 T 类型的对象
} else {
return Factory<ProductType, Types...>::create(typeName); // 递归调用
}
}
};
完整代码示例
下面是一个完整的示例代码,展示了如何使用编译期工厂来创建对象:
#include <iostream>
#include <string>
#include <stdexcept>
#include <typeinfo>
// 所有产品类的基类
class BaseProduct {
public:
virtual ~BaseProduct() = default;
virtual void doSomething() = 0;
};
// 具体产品类 A
class ConcreteProductA : public BaseProduct {
public:
void doSomething() override { std::cout << "Product A" << std::endl; }
static constexpr const char* typeName = "ConcreteProductA";
};
// 具体产品类 B
class ConcreteProductB : public BaseProduct {
public:
void doSomething() override { std::cout << "Product B" << std::endl; }
static constexpr const char* typeName = "ConcreteProductB";
};
// 对象创建函数
template<typename T>
BaseProduct* createObject() {
return new T();
}
template<typename ProductType, typename... Types>
class Factory; // 前置声明
// 特化版本,处理空类型列表
template<typename ProductType>
class Factory<ProductType> {
public:
static ProductType* create(const std::string& typeName) {
throw std::runtime_error("Unknown type: " + typeName);
}
};
// 通用版本,递归处理类型列表
template<typename ProductType, typename T, typename... Types>
class Factory<ProductType, T, Types...> {
public:
static ProductType* create(const std::string& typeName) {
if (typeName == T::typeName) {
return createObject<T>(); // 使用 createObject 创建 T 类型的对象
} else {
return Factory<ProductType, Types...>::create(typeName); // 递归调用
}
}
};
// 类型列表
template<typename... Types>
struct TypeList {};
int main() {
using MyProductList = TypeList<ConcreteProductA, ConcreteProductB>;
// 使用工厂创建对象
BaseProduct* productA = Factory<BaseProduct, ConcreteProductA, ConcreteProductB>::create("ConcreteProductA");
BaseProduct* productB = Factory<BaseProduct, ConcreteProductA, ConcreteProductB>::create("ConcreteProductB");
if (productA) {
productA->doSomething(); // 输出 "Product A"
delete productA;
}
if (productB) {
productB->doSomething(); // 输出 "Product B"
delete productB;
}
try {
BaseProduct* productC = Factory<BaseProduct, ConcreteProductA, ConcreteProductB>::create("ConcreteProductC");
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
更高级的用法:自动注册
上面的例子中,我们需要手动把所有的类型都添加到类型列表中。如果类型很多,或者类型经常变化,手动维护类型列表就变得很麻烦。为了解决这个问题,我们可以使用一些技巧来实现自动注册。
基本思路是:
- 定义一个全局的类型列表。
- 在每个类的定义中,使用一个静态变量来把自己注册到类型列表中。
#include <iostream>
#include <string>
#include <stdexcept>
#include <typeinfo>
// 所有产品类的基类
class BaseProduct {
public:
virtual ~BaseProduct() = default;
virtual void doSomething() = 0;
};
// 对象创建函数
template<typename T>
BaseProduct* createObject() {
return new T();
}
// 类型列表
template<typename... Types>
struct TypeList {};
// 用于累积类型的模板
template <typename... Ts>
struct TypeListAccumulator {
template <typename T>
using Add = TypeListAccumulator<Ts..., T>;
using List = TypeList<Ts...>;
};
// 全局类型列表
using ProductTypeList = TypeListAccumulator<>;
// 注册宏
#define REGISTER_PRODUCT(ProductType)
namespace {
struct ProductType##Register {
ProductType##Register() {
using Accumulator = decltype(ProductTypeList{});
using NewAccumulator = typename Accumulator::template Add<ProductType>;
using NewList = typename NewAccumulator::List;
ProductTypeList = NewAccumulator{};
}
};
static ProductType##Register productType##ProductType##Register;
}
template<typename ProductType, typename... Types>
class Factory; // 前置声明
// 特化版本,处理空类型列表
template<typename ProductType>
class Factory<ProductType> {
public:
static ProductType* create(const std::string& typeName) {
throw std::runtime_error("Unknown type: " + typeName);
}
};
// 通用版本,递归处理类型列表
template<typename ProductType, typename T, typename... Types>
class Factory<ProductType, T, Types...> {
public:
static ProductType* create(const std::string& typeName) {
if (typeName == typeid(T).name()) {
return createObject<T>(); // 使用 createObject 创建 T 类型的对象
} else {
return Factory<ProductType, Types...>::create(typeName); // 递归调用
}
}
};
// 具体产品类 A
class ConcreteProductA : public BaseProduct {
public:
void doSomething() override { std::cout << "Product A" << std::endl; }
};
REGISTER_PRODUCT(ConcreteProductA);
// 具体产品类 B
class ConcreteProductB : public BaseProduct {
public:
void doSomething() override { std::cout << "Product B" << std::endl; }
};
REGISTER_PRODUCT(ConcreteProductB);
// 全局类型列表的实例
ProductTypeList ProductTypeList;
int main() {
using RegisteredTypes = decltype(ProductTypeList)::List;
// 使用工厂创建对象
BaseProduct* productA = Factory<BaseProduct, ConcreteProductA, ConcreteProductB>::create(typeid(ConcreteProductA).name());
BaseProduct* productB = Factory<BaseProduct, ConcreteProductA, ConcreteProductB>::create(typeid(ConcreteProductB).name());
if (productA) {
productA->doSomething(); // 输出 "Product A"
delete productA;
}
if (productB) {
productB->doSomething(); // 输出 "Product B"
delete productB;
}
try {
BaseProduct* productC = Factory<BaseProduct, ConcreteProductA, ConcreteProductB>::create("ConcreteProductC");
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
总结
今天我们一起探索了C++编译期工厂模式的奥秘,特别是基于类型列表的实现方式。我们了解到:
- 编译期工厂可以提高性能、保证类型安全、增强灵活性。
- 类型列表是编译期工厂的基础,可以用模板元编程来实现。
- 模板元编程是编译期工厂的核心,可以让我们在编译期进行计算和代码生成。
- 需要注意
typeid
在不同编译器下的差异,并使用宏来定义类型名。 - 可以使用自动注册的技巧,简化类型列表的维护。
希望今天的分享能帮助你更好地理解和应用C++编译期工厂模式。这玩意儿虽然有点烧脑,但是一旦掌握了,就能让你的代码变得更加优雅和高效。 以后有机会,咱们再聊聊更高级的模板元编程技巧!