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


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

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

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

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


11434
2017-09-03 08:22


起源



答案:


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

对于访客来说,继承自 boost::static_visitor<>

实际上,我使访问者和通用方法的使用范围更广。

我创造了一个 class MyOption 有描述, boost::variant 对于值和其他选项,如隐式,默认等。我填充了该类型的对象矢量 MyOption 就像PO那样为他们的选择做同样的事情(见 boost::po::options_add())通过模板。在传递的那一刻 std::string() 要么 double() 对于 boosts::varian初始化你填写值的类型和其他东西,如默认,隐式。

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

接下来你需要执行 po::parse 和 po::notify。之后,您将可以使用已填充的 std::vector<MyOption*> 通过访客模式,因为它持有boost :: variant里面。

所有这些都有什么好处 - 你必须在代码中只编写一次你的选项类型 - 填写你的 std::vector<MyOption*>

PS。如果使用这种方法,您将面临为没有值的选项设置notifyer的问题,请参阅此主题以获得解决方案: boost-program-options:没有值的选项的通知程序

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

6
2017-09-04 17:15



谢谢您的详尽解释。这也是一个有趣的解决方案。通过这种方式,我还可以轻松地为帮助输出添加不同的描述,并简单地列出选项值。 - shiin


答案:


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

对于访客来说,继承自 boost::static_visitor<>

实际上,我使访问者和通用方法的使用范围更广。

我创造了一个 class MyOption 有描述, boost::variant 对于值和其他选项,如隐式,默认等。我填充了该类型的对象矢量 MyOption 就像PO那样为他们的选择做同样的事情(见 boost::po::options_add())通过模板。在传递的那一刻 std::string() 要么 double() 对于 boosts::varian初始化你填写值的类型和其他东西,如默认,隐式。

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

接下来你需要执行 po::parse 和 po::notify。之后,您将可以使用已填充的 std::vector<MyOption*> 通过访客模式,因为它持有boost :: variant里面。

所有这些都有什么好处 - 你必须在代码中只编写一次你的选项类型 - 填写你的 std::vector<MyOption*>

PS。如果使用这种方法,您将面临为没有值的选项设置notifyer的问题,请参阅此主题以获得解决方案: boost-program-options:没有值的选项的通知程序

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

6
2017-09-04 17:15



谢谢您的详尽解释。这也是一个有趣的解决方案。通过这种方式,我还可以轻松地为帮助输出添加不同的描述,并简单地列出选项值。 - shiin


使用访客模式是一个很好的例子。不幸 boost::any 不支持访客模式 boost::variant确实。不过还有一些第三方 方法

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


5
2017-09-03 08:58



感谢您关于RTTI的链接和想法。我希望我可以阻止为所有支持的类型构建一个结构,如果类型增加我将不得不管理,但似乎这是不可能的。基本上,我想把钱传递给类型 - 就像它们支持'operator <<'一切正常,否则编译应该失败。 - shiin


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

我想出的方法是尝试一堆<...>()然后忽略异常。它并不是非常漂亮,但我让它发挥作用。

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

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

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种类型用于从命令行/配置文件中获取信息,如果我添加更多类型,我将不得不添加更多行。我承认这有点难看。


0
2017-09-04 15:33





因为无论如何你只是打印它们,你可以在解析时获取原始字符串表示。 (可能代码中存在编译器错误,我将其从我的代码库中删除并且没有类型化的东西)

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

0
2017-09-04 16:14