从命令行创建通用对象 [英] Generic object creation from command line

查看:66
本文介绍了从命令行创建通用对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个C ++抽象类 Abs1 Abs2 。然后我有:

I have two C++ abstract classes Abs1 and Abs2. Then I have:

`A : public Abs1`
`B : public Abs1` 
`C : public Abs2`
`D : public Abs2`

现在,我正在尝试根据命令行参数创建对象,我必须重写public factory函数链接问题中的 make_abstract ,例如:

Now, I'm trying to create objects from command line arguments and I have to do rewrite the public factory function make_abstract in the linked question, something like:

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);
    }
}

如您所见,这是非常多余的。我该如何做一个通用版本?在此版本中,我们可以根据需要传递尽可能多的实现类,因此 if 级联不是解决方案。注意,我们不能修改这些类中的任何一个。

As you can see this is terribly redundant. How can I do a generic version of this? In this version we can pass as many implemented class as we want, so the if cascade is not a solution. Notice that we cannot modify any of these classes.

我当时以为也许可变参数模板可以提供帮助,但我想不出很多问题:

I was thinking that maybe variadic templates could help, but I can't figure out many problems:

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:底部有一个实时示例]

[TL;DR: there's a live example at the bottom]

我有在 detail :: make_abstract 函数的顶部实现了两层映射。让我们从调用代码开始:

I have implemented two layers of mapping on top of your detail::make_abstract function(s). Let's start from the calling code:

int main(int argc, char **argv) {
    std::unique_ptr<Abs1> p1;
    std::unique_ptr<Abs2> p2;

    makeEverything(argc, argv, p1, p2);
}

在这里,我们正在调用 makeEverything ,其中包含 argc argv std :: unique_ptr s。函数结束后,指针之一将保存正确类型的对象。

Here we are calling makeEverything with argc, argv, and a list of std::unique_ptrs. After the function ends, one of the pointers will hold an object of the correct type.

让我们更深入。

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...);
}

这是您通常的递归可变参数函数模板:以第一个指针为例,尝试为此构造一个对象。如果失败,则将其丢弃并重试下一个。您可以在顶部的基本情况下的重载中放置一些错误处理:当根本无法构造任何对象时,将调用它。

This is your usual recursive variadic function template: take the first pointer, try to construct an object for it. If it failed, throw it away and retry with the next one. You could put some error handling inside the base-case overload at the top: it will be called when no object could be constructed at all.

所以现在我们知道哪个 Abs1 Abs2 或所需的基类。

让我们更深入。 / p>

So now we know which one of Abs1, Abs2 or whatever is the desired base class.
Let's go deeper.

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);
}

makeAbs 检查并检索 argv [1] 。然后它将它用作工厂功能映射的键,以检索与该名称相对应的工厂,然后调用它并返回结果对象。

makeAbs checks and retrieves argv[1]. Then it uses it as a key into a map of factory functions, to retrieve the factory corresponding to that name, and then call it and return the resulting object.

如果没有该名称的对象是已知的, std :: map :: at()将抛出 std :: out_of_bounds 。当然,您可以更改错误处理

If no object of that name is known, std::map::at() will throw std::out_of_bounds. Of course, you can change that error handling

现在让我们看看如何填充工厂地图,这实际上很简单:

Now let's see how we can populate the factory maps, it is actually quite easy:

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>>}
};

您只需要提供 FactoryMap< Abs> :: map的定义您要使用的每个 Abs 。由于这是一个对象定义,因此应将其放在.cpp文件中。请注意,作为奖励,您可以添加新的类及其映射而无需重新编译其他任何内容!

You just have to provide a definition of FactoryMap<Abs>::map for each Abs you wish to use. Since this is an object definition, this should be put inside a .cpp file. Note that, as a bonus, you can add new classes and their mappings without recompiling anything else!

拼图的最后一块: detail :: make_abstract_erased 。您尚未提供 detail :: make_abstract 的声明,但看起来它返回了 std :: unique_ptr< T> ,其中 T 是其第一个模板参数。

Final piece of the puzzle: detail::make_abstract_erased. You haven't provided the declaration of detail::make_abstract, but it looks like it returns std::unique_ptr<T>, with T being its first template argument.

鉴于C ++不允许在函数指针之间进行转换不同的返回类型(出于充分的原因),我们需要附加一层来包装 detail :: make_abstract 并执行转换:

Given that C++ does not allow converting between function pointers that differ in return types (and for good reasons), we need that additional layer just to wrap detail::make_abstract and perform the conversion:

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上实时观看

这篇关于从命令行创建通用对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆