Generic object creation from command line
我有两个C抽象类
1 2 3 4 | `A : public Abs1` `B : public Abs1` `C : public Abs2` `D : public Abs2` |
现在,我正在尝试从命令行参数创建对象,并且必须在链接的问题中重写公共工厂函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | std::unique_ptr<Abs1> makeAbs1 (int argc, const char*argv[]) { if (argc == 1) { return nullptr; } const std::string name = argv[1]; if (name =="A") { return detail::make_abstract<A, std::tuple<int, std::string, int>>(argc, argv); } else if (name =="B") { return detail::make_abstract<B, std::tuple<int, int>>(argc, argv); } } std::unique_ptr<Abs2> makeAbs2 (int argc, const char*argv[]) { if (argc == 1) { return nullptr; } const std::string name = argv[1]; if (name =="C") { return detail::make_abstract<C, std::tuple<int>>(argc, argv); } else if (name =="D") { return detail::make_abstract<D, std::tuple<int, float>>(argc, argv); } } |
如您所见,这是非常多余的。我该如何做一个通用版本?在此版本中,我们可以根据需要传递尽可能多的实现的类,因此
我当时以为可变参数模板可能会有所帮助,但我想不出很多问题:
1 2 3 4 5 6 7 8 | template <typename T, typename ...Ts> std::unique_ptr< T > make (int argc, const char*argv[]){ const std::string name = argv[1]; for(Ti : Ts) //this is obviously wrong if(typeid(Ti).name == name) return detail::make_abstract<T, std::tuple</*Here shoudl be different for every Ti*/>>(argc, argv); } |
哦,这很有趣:)
[TL; DR:底部有一个实时示例]
我在
1 2 3 4 5 6 | int main(int argc, char **argv) { std::unique_ptr<Abs1> p1; std::unique_ptr<Abs2> p2; makeEverything(argc, argv, p1, p2); } |
在这里,我们用
让我们更深入。
1 2 3 4 5 6 7 8 9 10 11 | inline void makeEverything(int, char**) { } template <class Abs, class... Abses> void makeEverything(int argc, char **argv, std::unique_ptr<Abs> &abs, std::unique_ptr<Abses> &... abses) { abs = makeAbs<Abs>(argc, argv); if(!abs) makeEverything(argc, argv, abses...); } |
这是您通常使用的递归可变参数函数模板:获取第一个指针,尝试为其构造一个对象。如果失败,则将其丢弃并重试下一个。您可以在顶部的基本情况重载内部进行一些错误处理:当根本无法构造任何对象时,将调用该错误处理。
因此,现在我们知道了
让我们更深入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | template <class Abs> using Factory = std::unique_ptr<Abs>(int, char **); template <class Abs> using FactoryMap = std::map<std::string, Factory<Abs>*>; template <class Abs> struct Factories { static const FactoryMap<Abs> map; }; template <class Abs> std::unique_ptr<Abs> makeAbs(int argc, char **argv) { if (argc < 2) return nullptr; return Factories<Abs>::map.at(argv[1])(argc, argv); } |
如果不知道该名称的对象,则
现在让我们看看如何填充工厂地图,这实际上非常简单:
1 2 3 4 5 | template <> FactoryMap<Abs1> const Factories<Abs1>::map { {"A", detail::make_abstract_erased<Abs1, A, std::tuple<int, std::string, int>>}, {"B", detail::make_abstract_erased<Abs1, B, std::tuple<int, int>>} }; |
您只需为要使用的每个
难题的最后一部分:
鉴于C不允许在返回类型不同的函数指针之间进行转换(出于充分的原因),我们需要额外的一层来package
1 2 3 4 5 6 | namespace detail { template <class Abs, class T, class Params> std::unique_ptr<Abs> make_abstract_erased(int argc, char **argv) { return make_abstract<T, Params>(argc, argv); } } |
就是这样!
在Coliru上实时观看