13

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

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

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

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

4

4 回答 4

7

正如@Rost 之前提到的,访客模式在这里是一个不错的选择。要将它与 PO 一起使用,您需要为您的选项使用通知程序,这样如果通过了选项,通知程序将在您的boost::variant值集中填充一个条目。该套装应单独存放。之后,您可以迭代您的集合并使用boost::apply_visitor.

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

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

我创建了一个class MyOption包含描述、boost::variant值和其他选项(如隐式、默认等)的内容。MyOption我用与 PO 一样的方式填充该类型对象的向量(参见boost::po::options_add()参考资料)。在传递std::string()double()boosts::variant 初始化的那一刻,您填充值的类型和其他内容,如默认、隐式。

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

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

所有这一切有什么好处——你必须在代码中只写一次你的选项类型——在填充你的std::vector<MyOption*>.

PS。如果使用这种方法,您将面临为没有值的选项设置通知器的问题,请参阅此主题以获取解决方案:boost-program-options: notifier for options with no value

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));  
于 2012-09-04T17:15:06.350 回答
5

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

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

于 2012-09-03T08:58:28.743 回答
2

由于无论如何您都将它们打印出来,因此您可以在解析时获取原始字符串表示形式。(代码中可能存在编译器错误,我把它从我的代码库和未定义类型的一堆东西中撕下来)

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
于 2014-09-04T16:14:27.127 回答
0

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

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

在下面的代码块中,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,依此类推。

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

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

于 2014-09-04T15:33:56.707 回答