c++类工厂宏

C++ class factory macro

本文关键字:工厂 c++      更新时间:2023-10-16

我想创建一个宏来轻松创建新的类,这些类派生自相同的基类,但名称不同,行为略有不同。

我已经有

class FactoryBonusModifier
{
public:
   /// Function to overload
   virtual BonusModifierAbstract* createBonus() const = 0;
protected:
};
#define DEFAULT_BONUS_FACTORY_DECLARATION(FactoryName)      
class Factory## FactoryName : public FactoryBonusModifier   
{                                                           
public:                                                     
    virtual BonusModifierAbstract* createBonus() const;     
};
#define DEFAULT_BONUS_FACTORY_IMPLEMENTATION(FactoryName)                 
    BonusModifierAbstract* Factory## FactoryName::createBonus() const    
    {  return new FactoryName();  }

DEFAULT_BONUS_FACTORY_DECLARATION(BonusModifierGoThroughWall);

和在cpp中编写的实现部分。

我想知道我是否可以有一个宏,用尽可能少的复制/粘贴来构建这些新类的枚举和数组。

最后我想要像

这样的东西
enum BonusType{
Bonus1,
Bonus2,
...,
Nb_Bonus
};
FactoryBonusModifier* factories[Nb_Bonus] = 
{
    new FactoryBonus1(),
    new FactoryBonus2(),
    ...,
}

如果可以的话,我会避免使用宏。我会用模板代替。

class FactoryBonusModifier
{
public:
   /// Function to overload
   virtual BonusModifierAbstract* createBonus() const = 0;
protected:
};
template<typename Bonus> 
class Factory : public FactoryBonusModifier
{
public:
    virtual Bonus* createBonus() const
    {
       return new Bonus();
    }
}

所以棘手的部分是创建enum和工厂列表。为此,我将生成代码,而不是尝试使用模板。您可以很容易地将代码生成集成到大多数构建过程中。但是你需要仔细阅读。

我个人会使用非常简单的python脚本,比如

bonuses = ["BonusModifierGoThroughWall", "SomeOtherBonus" ];
print "enum BonusType {"
for x in bonuses:
  print "t"+x+","
print "nBonusTypes"
print "};"
print "FactoryBonusModifier* factories["+len(bonuses)+"] = {"
for x in bonuses:
  print "new Factory<"+bonus+">(),"
print "};"

输出:

enum BonusType {
  tBonusModifierGoThroughWall,
  tSomeOtherBonus,
  nBonusTypes
};
FactoryBonusModifier* factories[2] = {
  new Factory<BonusModifierGoThroughWall>(),
  new Factory<SomeOtherBonus>(),
};

这是对@MichaelAnderson的解决方案的扩展。

执行相同的FactoryBonusModifier,但不生成enum和python中的数组。在模板元编程中完成。

首先是一些样板文件。这些东西只是模板元编程的工具包:

template<typename...>
struct type_list {};
// get the nth type from a list:
template<int n, typename list>
struct nth_type;
// the 0th type is the first type:
template<0, typename t0, typename... types>
struct nth_type<0, type_list<t0,types...>> {
  typedef t0 type;
};
// the nth type is the n-1th type of the tail of the list:
template<int n, typename t0, typename... types>
struct nth_type<n, type_list<t0,types...>>:nth_type<n-1,type_list<types...>>
{};
// Get the index of T in the list.  3rd parameter is for metaprogramming.
template<typename T, typename list, typename=void>
struct index_in;
// If T is the first type in the list, the index is 0
template<typename T, typename t0, typename... list>
struct index_in<T, type_list<t0, list...>, typename std::enable_if<std::is_same<T,t0>::value>::type> {
  enum {value = 0};
};
// If T is not the first type in the list, the index is 1 plus the index of T
// in the tail of the list:
template<typename T, typename t0, typename... list>
struct index_in<T, type_list<t0, list...>, typename std::enable_if<!std::is_same<T,t0>::value>::type> {
  enum {value = index_in<T, type_list<list...>>::value+1};
};
// calls () on the arguments passed to it in order:
inline void do_in_order() {}
template<typename L0, typename... Lambdas>
void do_in_order( L0&& l0, Lambdas&&... lambdas ) {
  std::forward<L0>(l0)(); // std::forward is for insane corner cases, not usually needed
  do_in_order( std::forward<Lambdas>(lambdas)... );
}

我们有type_list,它打包了一个类型列表来传递或操作。我们在type_list上有两个操作,nth_type从列表的索引中提取类型,index_in获取类型并返回其索引。

最后,我们有一个名为do_in_order的辅助函数,我们可以使用它与参数包展开一起迭代参数包,并对参数包中的每个元素执行操作。与其他hack相比,我更喜欢它,因为它很容易编写,而且产生的意外也更少。

一旦我们有了这些基本的工具,我们就可以很容易地编写一个Factory Factory:

// bad name for this template.  It takes the type list list and applies
// the producer template on each, then stores pointers to instances of those
// in an array of base pointers (well unique_ptrs).  It also provides
// a type-to-index mapping, an index-to-instance mapping, and a type-to
// instance mapping:
template<typename list, template<typename>class producer, typename base>
struct mapping;
template<typename... Ts, template<typename>class producer, typename base>
struct mapping<type_list<Ts...>, producer, base>
{
  enum Enum {
    min_value = 0,
    max_value = sizeof...(list)
  };
  template<typename T>
  static Enum GetIndex() constexpr { return (Enum)index_in<T, type_list<Ts...>>::value; }
  std::unique_ptr<base> Array[max_value];
  mapping() {
    do_in_order( [&Array](){
      Array[ GetIndex<Ts>() ].reset( new producer<Ts>() );
    }... );
  }
  // typed get:
  template<typename T>
  producer<T>* GetItem() const {
    return static_cast<producer<T>*>( Array[GetIndex<T>].get() );
  }
  // index get:
  base* GetItem(std::size_t n) const {
    if (n >= max_value)
      return nullptr;
    return Array[n].get();
};

还不做任何事情。加上Michaels的回答:

class FactoryBonusModifier
{
public:
  /// Function to overload
  virtual BonusModifierAbstract* createBonus() const = 0;
protected:
};
template<typename Bonus> 
class Factory : public FactoryBonusModifier
{
public:
  virtual Bonus* createBonus() const
  {
    return new Bonus();
  }
};

然后将Factory和支持的类型列表提供给mapping:

type_list< ItemBonus, InheritBonus, TaxBonus, PerformanceBonus > BonusList;
typedef mapping< BonusList, Factory, FactoryBonusModifier > MetaFactory_t;
MetaFactory_t MetaFactory;

,我们做完了。

MetaFactory_t::Enum是一个类型,表示单个工厂,以及它们在数组中的偏移量。为了获得给定奖励类型的Enum值,MetaFactory_t::GetIndex<BonusType>()给出了这个值。

如果你想要一个给定奖励类型的工厂,MetaFactory.GetItem<BonusType>()将返回一个正确类型的指向Factory<BonusType>的指针。如果Enum的值为n,则MetaFactory.GetItem(n)返回指向FactoryBonusModifier基类的指针(如果n超出范围,则返回指向nullptr的指针)。

工厂生命周期由MetaFactory对象的生命周期管理。

简而言之,在模板出现之前,代码生成通常用于这类事情。但是可变模板使得整型<->类型映射非常容易,并且允许相当令人印象深刻的代码生成。

作为附带的好处,这个版本包含了许多python生成的版本所没有的类型安全。我甚至可以编写一个接受n的函数,并调用一个传入的函子,带一个指针指向所讨论的工厂的正确类型,从而产生更多的类型安全性。

现在,如果您有超过100个额外类型,则该技术将变得更加困难,因为会达到编译器递归限制。即使这样,也有一些技术允许以这种方式处理1000个类型的列表(然而,它们更尴尬)。

上面的代码没有被编译过,所以几乎可以肯定包含bug,但是基本的设计是可靠的——我以前做过这个。