我想创建一个模板工厂类,以便给定一个int i,它将在其参数列表中创建第i个类的实例;我必须是一个运行时变量。这个想法开始如下:假设我们有类型A,B。我们可以轻松地在下面创建类:
class factory{
void* create(size_t i) {
return (i==0) new A() : (i==1 ? new B() :nullptr);
}
};
我们必须使用void*
作为返回类型,但这可行。现在我们如何使用模板扩展它,以便我们可以做到:
auto f = factory<A,B,C,D,E,F>();
A* a = f.create(0);
E* e = f.create(4);
我已经在此上花费了10个小时,并提出了一个解决方案,如果在编译时知道我的话,该解决方案将起作用。这里是:
template <size_t i, class first, class... rest>
struct choose: public choose<i - 1, rest...> {};
template <class first, class... rest>
struct choose<0, first, rest...> {
using type = first;
};
template<class... cls>
class factory {
public:
template <size_t j, typename... ArgTypes>
inline typename choose<j, cls...>::type* create(ArgTypes... args) {
using f = typename choose<j, cls...>::type;
return new f(args...);
}
};
问题是变量j需要在编译时知道,这不是我想要的。我也不想创建对象,将它们放入数组然后从数组中获取它。我知道这会使我的痛苦减轻,但是此练习的目的是学习模板。
此代码的上下文适用于我使用c ++制作的简单Web框架。我想要控制器类和应用程序类:
class Controller {
virtual get(params) =0;
virtual post(params) =0;
};
class ControllerA: Controller {
public:
constexpr static char route[] = "/home"; //this controller handles this route
// virtual implementation
};
class ControllerB: Controller {
public:
constexpr static char route[] = "/user";
// virtual implementation
};
template<class... controllers>
class Application {
factory<controllers...> f;
public:
Application() {
//create router and give it all of the routes from the controllers in the template.
}
dispatchPostRequest(request) {
int i = doRounting(request.route); //router returns a number that specifies the
//index of controller in the template parameter pack.
f.create(i) -> post()
}
};
对我来说,这似乎是一个简单的概念。 Java / JS / Python的实现每个要花20分钟。我不明白为什么我在C ++中如此艰难。
我也不想创建对象,将它们放入数组[...]
这可能是解决方案的基础;尽管您不会将objects添加到该数组中,但是会添加creator functions。
然而,您非常简单的代码已经出现了内存泄漏:
f.create(i) -> post();
何时再次删除对象???因此,通过返回智能指针使创建者函数安全!
通过void*
进行操作也不是安全的(除了防止使用智能指针–您不能delete
无效指针!),您可以很容易地应用错误的类型转换:
std::string* s = static_cast<std::string*>(create<std::string, std::complex<double>, std::vector<int>>(1));
看到问题了吗?但是,有了您的Controller
基类,您已经[[have已经具有保持工厂功能(或类)安全所需的所有必要基础结构!因此,可能的解决方案如下所示:
template <typename T>
std::unique_ptr<Controller> makeT()
{
return std::make_unique<T>();
}
template <typename ... Types>
std::unique_ptr<Controller> create(size_t index)
{
static std::unique_ptr<Controller>(*creators[])() =
#if 0
{ &std::make_unique<Types> ... };
#else
{ &makeT<Types>... };
#endif
return creators[index]();
}
[不幸的是,我们不能直接使用std::make_unique
,因为每个实例都有不同的返回类型(特定类的unique_ptr!),因此我们得到了不兼容的指针。所以我们需要那个中间函数...由于智能指针(避免显式删除的必要性)和多态性(避免需要正确的强制转换),因此上面的调用现在是安全的。
<typename HEAD, typename... TAIL>
指定功能。这可能是用作实用程序的私有函数:template<class... cls>
class factory {
private:
template <typename Head, typename... Tail>
void* create_impl(size_t i) {
if (i == 0)
return new Head;
else
return create_impl<Tail...>(i - 1);
}
public:
void* create(size_t i) {
return create_impl<cls...>(i);
}
};
因此,您具有运行时参数i
以及编译时模板参数Head
和'Tail'。Tail
是可变参数。只要运行时参数为0,就会检索Head
。否则,您将使用递归。为了简单起见,我使用
void*
,您可能具有更复杂的层次结构。更新。>>好的,在第一个方法之上的另一种方法。您可以实现一个应被调用一次的函数,该函数将创建一个应基于索引调用其他函数的表。
template<class... cls> class factory { private: template <typename Head, typename... Tail> void* create_impl(size_t i) { if (i == 0) return new Head; else return create_impl<Tail...>(i - 1); } function<void*()> make_func(size_t i) { return make_func_impl(i); } template <typename Head, typename... Tail> function<void*()> make_func_impl(size_t i) { if (i == 0) return create<Head>; else return make_func_impl<Tail...>(i - 1); } template <typename Class> Class* create() { return new Class; } vector<function<void*()>> m_table; public: factory() { m_table.reserve(sizeof...(cls)); for (int i = 0; i < sizeof...(cls); ++i) { m_table[i] = make_func(i); } } void* create(size_t i) { return m_table[i](); } };
这仍然可能需要线性递归,但是将执行一次。您可以将m_table
用作基于运行时索引的函数的预先计算的表。