C++ 编译期工厂模式:基于类型列表的编译期对象创建

哈喽,各位好!今天咱们来聊聊一个C++里挺高级也挺酷的东西:编译期工厂模式,而且还是基于类型列表的。这东西听起来可能有点吓人,但其实只要你把它拆解开来,就会发现它并没有那么神秘,反而能让你在编译期玩出很多花样。

啥是工厂模式?为啥要编译期?

首先,咱们先简单回顾一下工厂模式。简单来说,工厂模式就是把对象的创建过程给封装起来。你不用关心对象是怎么被new出来的,只要告诉工厂你想要啥,工厂就会帮你把东西搞定。这样一来,你的代码就变得更灵活、更易于维护。

传统的工厂模式通常是在运行时工作的,也就是程序跑起来的时候才决定创建哪个对象。但有时候,我们希望能在编译的时候就把这些事情确定下来。这样做的好处是:

  • 性能更高: 编译期完成的事情,运行时就不用做了,可以省下不少时间。
  • 类型安全: 编译期就能检查类型错误,避免运行时出现一些莫名其妙的问题。
  • 更灵活: 可以根据编译时的条件,选择不同的对象创建方式。

类型列表:编译期工厂的基石

要实现编译期工厂,类型列表(Type List)是必不可少的工具。类型列表本质上就是一个包含了若干类型的列表,但这个列表是在编译期就确定的。C++11之后,我们可以用模板元编程来实现类型列表。

一个简单的类型列表可能是这样的:

template<typename... Types>
struct TypeList {};

using MyTypeList = TypeList<int, double, std::string>;

这里,TypeList就是一个模板类,可以接受任意数量的类型作为模板参数。MyTypeList就是一个具体的类型列表,包含了intdoublestd::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模板类有两个版本:

  1. 特化版本: 当类型列表为空时,会抛出一个异常,表示找不到对应的类型。
  2. 通用版本: 递归地处理类型列表。如果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;
}

更高级的用法:自动注册

上面的例子中,我们需要手动把所有的类型都添加到类型列表中。如果类型很多,或者类型经常变化,手动维护类型列表就变得很麻烦。为了解决这个问题,我们可以使用一些技巧来实现自动注册。

基本思路是:

  1. 定义一个全局的类型列表。
  2. 在每个类的定义中,使用一个静态变量来把自己注册到类型列表中。
#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++编译期工厂模式。这玩意儿虽然有点烧脑,但是一旦掌握了,就能让你的代码变得更加优雅和高效。 以后有机会,咱们再聊聊更高级的模板元编程技巧!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注