c++中类模板的自动编译时工厂注册



我正在寻找类模板的抽象工厂,其中类在静态初始化时自动注册自己。对于常规(非模板化)类,使用静态成员的解决方案非常简单。下面是一个(相当简单的)解决方案的例子,它工作得很好:

#include <cassert>
#include <iostream>
class Base {
 public:
  virtual size_t id() const = 0;
  virtual const char* name() const = 0;
  virtual ~Base() {}
};
typedef Base* (*CreateFunc)(void);
class SimpleFactory {
 private:
  static const size_t NELEM = 2;
  static size_t id_;
  static CreateFunc creators_[NELEM];
 public:
  static size_t registerFunc(CreateFunc creator) {
    assert(id_ < NELEM);
    assert(creator);
    creators_[id_] = creator;
    return id_++;
  }
  static Base* create(size_t id) { assert(id < NELEM); return (creators_[id])(); }
};
size_t SimpleFactory::id_ = 0;
CreateFunc SimpleFactory::creators_[NELEM];

class D1 : public Base {
 private:
  static Base* create() { return new D1; }
  static const size_t id_;
 public:
  size_t id() const { return id_; }
  const char* name() const { return "D1"; }
};
const size_t D1::id_ = SimpleFactory::registerFunc(&create);
class D2 : public Base {
 private:
  static Base* create() { return new D2; }
  static const size_t id_;
 public:
  size_t id() const { return id_; }
  const char* name() const { return "D2"; }
};
const size_t D2::id_ = SimpleFactory::registerFunc(&create);
int main() {
  Base* b1 = SimpleFactory::create(0);
  Base* b2 = SimpleFactory::create(1);
  std::cout << "b1 name: " << b1->name() << "tid: " << b1->id() << "n";
  std::cout << "b2 name: " << b2->name() << "tid: " << b2->id() << "n";
  delete b1;
  delete b2;
  return 0;
}

我的问题是如何使它工作时,我想注册/创建的东西更像:

template <typename T> class Base...
template <typename T> class D1 : public Base<T> ...

我能想到的最好的办法是把工厂也模板化,像这样:

 template <typename T>
 class SimpleFactory {
 private:
  static const size_t NELEM = 2;
  static size_t id_;
  typedef Base<T>* Creator;
  static Creator creators_[NELEM];
...(the rest remains largely the same)

但是我想知道是否有更好的方法,或者是否有人以前实现过这样的模式。

编辑:几年后重新审视这个问题(使用可变模板),我可以通过简单地"注册"函数,或者更确切地说是类,作为工厂的模板参数来更接近我想要的。它看起来像这样:
#include <cassert>
struct Base {};
struct A : public Base {
  A() { std::cout << "A" << std::endl; }
};
struct B : public Base {
  B() { std::cout << "B" << std::endl; }
};
struct C : public Base {
  C() { std::cout << "C" << std::endl; }
};
struct D : public Base {
  D() { std::cout << "D" << std::endl; }
};

namespace {
  template <class Head>
  std::unique_ptr<Base>
  createAux(unsigned id)
  {
    assert(id == 0);
    return std::make_unique<Head>();
  }
  template <class Head, class Second, class... Tail>
  std::unique_ptr<Base>
  createAux(unsigned id)
  {
    if (id == 0) {
      return std::make_unique<Head>();
    } else {
      return createAux<Second, Tail...>(id - 1);
    }
  }
}
template <class... Types>
class LetterFactory {
 public:
  std::unique_ptr<Base>
  create(unsigned id) const
  {
    static_assert(sizeof...(Types) > 0, "Need at least one type for factory");
    assert(id < sizeof...(Types));
    return createAux<Types...>(id);
  }
};
int main() {
  LetterFactory<A, B, C, D> fac;
  fac.create(3);
  return 0;
}

现在,这只是一个简单的原型,所以不要介意create()的线性复杂性。然而,这种设计的主要缺点是不允许使用任何构造函数参数。理想情况下,我不仅可以注册工厂需要创建的类,还可以注册每个类在其构造函数中接受的类型,并让create()以不同的方式接受它们。以前有人做过这样的事吗?

我在GameDev上发布了一个类似问题的答案,但解决方案不是编译时间。你可以点击这里查看:
> 17759年https://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct-components/17759

我不认为有任何方法可以使这个编译时间。基类内部的"id"实际上只是RTTI的简化形式,根据定义,它是运行时的。也许如果你把id作为模板参数…但是这会使其他事情变得更加复杂。

做更简单的事情会使你的意图更明显。

int main() {
  RegisterConcreteTypeFoo();
  RegisterConcreteTypeBar();
  // do stuff...
  CleanupFactories();
  return 0;
}

当这些init函数实际上被调用时(不是在编译时),它们失败了,你不会得到所有让调试更容易的很容易的东西。像堆栈跟踪。

在这种情况下,您还假定不希望以不同的方式初始化它们。例如,对"自动注册"的任何东西进行单元测试是过于复杂的。

更少的魔法=更容易,更便宜的维护。

如果这还不够,还有技术问题。编译器喜欢从库中去掉未使用的符号。可能有一个编译器特定的诡计来绕过它,我不确定。希望它能以一致的方式进行,而不是在开发周期中没有明显原因地随机进行。

最新更新