我正在为类模板寻找一个抽象工厂,其中类在静态初始化时自动注册。对于常规(非模板化)类,使用静态成员的解决方案非常简单。这是一个(相当简单)解决方案的例子,它可以很好地工作:
#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()可变地获取它们。有没有人曾经做过这样的事情?
答案 0 :(得分:2)
我在GameDev上发布了类似问题的答案,但解决方案不是编译时间。你可以在这里查看:
&GT; https://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct-components/17759#17759
我认为甚至没有办法让这个编译时间。基类内部的“id”实际上只是RTTI的简化形式,根据定义是运行时。也许如果你把id作为模板参数...但这会使其他一些东西变得更加复杂。
答案 1 :(得分:-1)
做更简单的事情会减少并使你的意图明显。
int main() {
RegisterConcreteTypeFoo();
RegisterConcreteTypeBar();
// do stuff...
CleanupFactories();
return 0;
}
当实际调用这些初始化函数(而不是在编译时)并且它们失败时,您将无法获得使调试更容易的所有非常简单的事情。就像堆栈跟踪一样。
在这种情况下,您还假设您不希望以不同方式初始化它们。例如,单独测试“自动注册”任何东西都过于复杂。
减少魔力=更容易,更便宜的维护。
如果这还不够,那么也存在技术问题。编译器喜欢从库中删除未使用的符号。可能有编译器特定的shenanigan绕过它,我不确定。希望它以一致的方式完成它,而不是在开发周期的中间没有明显的原因。