C ++模板和运行时

问题描述 投票:1回答:3

我想创建一个模板工厂类,以便给定一个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 ++中如此艰难。

c++ templates
3个回答
0
投票

我也不想创建对象,将它们放入数组[...]

这可能是解决方案的基础;尽管您不会将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!),因此我们得到了不兼容的指针。所以我们需要那个中间函数...

由于智能指针(避免显式删除的必要性)和多态性(避免需要正确的强制转换),因此上面的调用现在是安全的。


0
投票
您应使用<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用作基于运行时索引的函数的预先计算的表。

0
投票
我也想出了这两个衬里:
© www.soinside.com 2019 - 2024. All rights reserved.