boost::program_options:迭代并打印所有选项

boost::program_options : iterating over and printing all options

本文关键字:打印 选项 迭代 program options boost      更新时间:2023-10-16

我最近开始使用boost::program_options,发现它非常方便。也就是说,有一件事我无法用一种好的方式给自己编码:

我想迭代boost::program_options::variables_map中收集的所有选项,将它们输出到屏幕上。这应该成为一个方便的函数,我可以简单地调用它来列出所有设置的选项,而无需在添加新选项或为每个程序更新函数。

我知道我可以检查和输出单个选项,但如上所述,这应该成为一个忽略实际选项的通用解决方案。我进一步知道我可以迭代variables_map的内容,因为它只是一个扩展的std::map。然后,我可以检查存储的boost::any变量中包含的类型,并使用.as<>将其转换回适当的类型。但这意味着编码一个长的开关块,每种类型都有一个案例。在我看来,这不是一种好的编码风格

所以问题是,有没有更好的方法来迭代这些选项并输出它们?

正如@Rost之前提到的,访问者模式是一个不错的选择。要将其与PO一起使用,您需要为您的选项使用通知程序,这样,如果传递了选项,通知程序将在您的boost::variant值集中填充一个条目。该套装应单独存放。之后,您可以迭代您的集合,并使用boost::apply_visitor自动处理它们上的操作(即打印)。

对于访客,从boost::static_visitor<> 继承

实际上,我使Visitor和泛型方法的使用范围更广。

我创建了一个class MyOption,它包含描述,boost::variant代表值和其他选项,如隐式、默认等。我通过模板填充MyOption类型的对象向量,就像PO填充它们的选项一样(参见boost::po::options_add())。在传递std::string()double()进行boosts::variant初始化时,您可以填充值的类型和其他内容,如默认值、隐式。

之后,我使用Visitor模式填充boost::po::options_description容器,因为boost::po需要自己的结构来解析输入命令行。在填充过程中,我为每个选项设置了notifyer——如果它将被传递,boost::po将自动填充我的原始对象MyOption

接下来,您需要执行po::parsepo::notify。之后,您将能够通过Visitor模式使用已经填充的std::vector<MyOption*>,因为它内部包含boost::variant。

所有这些的好处是——在填充std::vector<MyOption*>时,您只需在代码中编写一次选项类型。

PS。如果使用这种方法,您将面临为无值选项设置notifyer的问题,请参阅本主题以获得解决方案:boost程序选项:无值选项的notifyer

PS2.代码示例:

std::vector<MyOptionDef> options;
OptionsEasyAdd(options)
  ("opt1", double(), "description1")
  ("opt2", std::string(), "description2")
...
;
po::options_descripton boost_descriptions;
AddDescriptionAndNotifyerForBoostVisitor add_decr_visitor(boost_descriptions);
// here all notifiers will be set automatically for correct work with each options' value type
for_each(options.begin(), options.end(), boost::apply_visitor(add_descr_visitor));  

使用Visitor模式是一个很好的例子。不幸的是,boost::any不像boost::variant那样支持访问者模式。尽管如此,还是有一些第三方方法。

另一个可能的想法是使用RTTI:创建映射到类型处理函数的已知类型的type_info的映射。

由于您无论如何都要打印它们,所以在解析时可以获取原始字符串表示。(很可能代码中有编译器错误,我把它从我的代码库中撕了出来,并对一堆东西进行了未键入的修改)

std::vector<std::string> GetArgumentList(const std::vector<boost::program_options::option>& raw)
{
    std::vector<std::string> args;
    BOOST_FOREACH(const boost::program_options::option& option, raw)
    {
        if(option.unregistered) continue; // Skipping unknown options
        if(option.value.empty())
            args.push_back("--" + option.string_key));
        else
        {
            // this loses order of positional options
            BOOST_FOREACH(const std::string& value, option.value)
            {
                args.push_back("--" + option.string_key));
                args.push_back(value);
            }
        }
    }
    return args;
}

用法:

boost::program_options::parsed_options parsed = boost::program_options::command_line_parser( ...
std::vector<std::string> arguments = GetArgumentList(parsed.options);
// print

我今天正处理这类问题。这是一个古老的问题,但也许这将帮助那些正在寻找答案的人。

我想出的方法是尝试一堆作为<…>(),然后忽略该异常。它不是很漂亮,但我已经做好了。

在下面的代码块中,vm是来自boost program_options的variables_map。vit是vm上的一个迭代器,使其成为一对std::string和boost::program_options::variable_value,后者是boost::any。我可以用vit->first打印变量的名称,但vit->second不太容易输出,因为它是一个boost::any,即原始类型已丢失。有些应该被强制转换为std::字符串,有些应该被转换为double,依此类推

所以,为了计算变量的值,我可以使用这个:

std::cout << vit->first << "=";
try { std::cout << vit->second.as<double>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<int>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<std::string>() << std::endl;
} catch(...) {/* do nothing */ }
try { std::cout << vit->second.as<bool>() << std::endl;
} catch(...) {/* do nothing */ }

我只有4种类型可以用来从命令行/config文件中获取信息,如果我添加更多的类型,我将不得不添加更多的行。我承认这有点难看。