一、说明
Boost.ProgramOptions
Boost.ProgramOptions 是一个可以轻松解析命令行选项的库,例如,控制台应用程序。如果您使用图形用户界面开发应用程序,命令行选项通常并不重要。
要使用 Boost.ProgramOptions 解析命令行选项,需要以下三个步骤:
-
定义命令行选项。您给它们命名并指定哪些可以设置为一个值。如果命令行选项被解析为键/值对,您还可以设置值的类型——例如,它是字符串还是数字。
-
使用解析器评估命令行。您可以从 main() 的两个参数获取命令行,这两个参数通常称为 argc 和 argv。
-
存储解析器评估的命令行选项。 Boost.ProgramOptions 提供了一个派生自 std::map 的类,它将命令行选项保存为名称/值对。之后,您可以检查存储了哪些选项以及它们的值是什么。
二、示例Boost.ProgramOptions
Example 63.1
示例 63.1。展示了使用 Boost.ProgramOptions 解析命令行选项的基本方法。
示例 63.1。 Boost.ProgramOptions 的基本方法
#include <boost/program_options.hpp>
#include <iostream>using namespace boost::program_options;void on_age(int age)
{std::cout << "On age: " << age << '\n';
}int main(int argc, const char *argv[])
{try{options_description desc{"Options"};desc.add_options()("help,h", "Help screen")("pi", value<float>()->default_value(3.14f), "Pi")("age", value<int>()->notifier(on_age), "Age");variables_map vm;store(parse_command_line(argc, argv, desc), vm);notify(vm);if (vm.count("help"))std::cout << desc << '\n';else if (vm.count("age"))std::cout << "Age: " << vm["age"].as<int>() << '\n';else if (vm.count("pi"))std::cout << "Pi: " << vm["pi"].as<float>() << '\n';}catch (const error &ex){std::cerr << ex.what() << '\n';}
}
要使用 Boost.ProgramOptions,请包含头文件 boost/program_options.hpp。您可以在命名空间 boost::program_options 中访问此库中的所有类和函数。
使用类 boost::program_options::options_description 来描述命令行选项。这种类型的对象可以写入诸如 std::cout 之类的流,以显示可用命令行选项的概览。传递给构造函数的字符串为概览提供了一个名称,作为命令行选项的标题。
boost::program_options::options_description 定义了一个成员函数 add() ,它需要一个 boost::program_options::option_description 类型的参数。您调用此函数来描述每个命令行选项。示例 63.1 不是为每个命令行选项调用此函数,而是调用成员函数 add_options(),这使得该任务更容易。
add_options() 返回一个代表 boost::program_options::options_description 类型对象的代理对象。代理对象的类型无关紧要。更有趣的是代理对象简化了许多命令行选项的定义。它使用重载运算符 operator(),您可以调用它来传递所需的数据以定义命令行选项。此运算符返回对同一代理对象的引用,这允许您多次调用 operator()。
Example 63.1
示例 63.1 在代理对象的帮助下定义了三个命令行选项。第一个命令行选项是 --help。此选项的说明设置为“帮助屏幕”。该选项是一个开关,而不是名称/值对。您可以在命令行上设置 --help 或忽略它。无法将 --help 设置为一个值。请注意,传递给 operator() 的第一个字符串是“help,h”。您可以为命令行选项指定简称。短名称必须仅由一个字母组成,并设置在逗号之后。现在可以使用 --help 或 -h 显示帮助。除了 --help 之外,还定义了另外两个命令行选项:--pi 和 --age。这些选项不是开关,它们是名称/值对。 --pi 和 --age 都应设置为一个值。您将指向类型为 boost::program_options::value_semantic 的对象的指针作为第二个参数传递给 operator() 以将选项定义为名称/值对。您不需要直接访问 boost::program_options::value_semantic。您可以使用辅助函数 boost::program_options::value(),它创建一个类型为 boost::program_options::value_semantic 的对象。 boost::program_options::value() 返回对象的地址,然后您可以使用 operator() 将其传递给代理对象。boost::program_options::value() 是一个函数模板,它将命令行选项值的类型作为模板参数。因此,命令行选项 --age 需要一个整数,而 --pi 需要一个浮点数。从 boost::program_options::value() 返回的对象提供了一些有用的成员函数。例如,您可以调用 default_value() 来提供默认值。如果未在命令行中使用该选项,则示例 63.1 将 --pi 设置为 3.14。
notifier() 将函数链接到命令行选项的值。然后使用命令行选项的值调用该函数。在示例 63.1 中,函数 on_age() 链接到 --age。如果命令行选项 --age 用于设置年龄,则年龄将传递给 on_age() 并将其写入标准输出。使用 on_age() 等函数处理值是可选的。您不必使用 notifier(),因为可以通过其他方式访问值。定义所有命令行选项后,您可以使用解析器。在示例 63.1 中,辅助函数 boost::program_options::parse_command_line() 被调用来解析命令行。此函数采用定义命令行的 argc 和 argv,以及包含选项说明的 desc。 boost::program_options::parse_command_line() 在 boost::program_options::parsed_options 类型的对象中返回解析后的选项。你通常不直接访问这个对象。相反,您将它传递给 boost::program_options::store(),它将解析的选项存储在容器中。示例 63.1 将 vm 作为第二个参数传递给 boost::program_options::store()。 vm 是 boost::program_options::variables_map 类型的对象。此类派生自类 std::map<std::string, boost::program_options::variable_value>,因此提供与 std::map 相同的成员函数。例如,您可以调用 count() 来检查某个命令行选项是否已被使用并存储在容器中。在示例 63.1 中,在访问 vm 和调用 count() 之前,调用了 boost::program_options::notify()。此函数触发诸如 on_age() 之类的函数,这些函数使用 notifier() 链接到一个值。如果没有 boost::program_options::notify(),将不会调用 on_age()。vm 可以让您检查某个命令行选项是否存在,还可以让您访问命令行选项设置的值。该值的类型是 boost::program_options::variable_value,一个在内部使用 boost::any 的类。您可以从成员函数 value() 中获取类型为 boost::any 的对象。示例 63.1 调用 as(),而不是 value()。此成员函数将命令行选项的值转换为作为模板参数传递的类型。 as() 使用 boost::any_cast() 进行类型转换。确保您传递给 as() 的类型与命令行选项的类型相匹配。例如,示例 63.1 期望将命令行选项 --age 设置为 int 类型的数字,因此必须将 int 作为模板参数传递给 as()。您可以通过多种方式启动示例 63.1。这是一个例子:
测试
在这种情况下,显示 Pi: 3.14。因为 --pi 未在命令行上设置,所以显示默认值。
此示例使用 --pi 设置一个值:
测试 --pi 3.1415
该程序现在显示 Pi:3.1415。
这个例子也传递了一个年龄:
测试 --pi 3.1415 --age 29
输出现在是 On age: 29 和 Age: 29。第一行是在调用 boost::program_options::notify() 时写入的;这会触发 on_age() 的执行。 --pi 没有输出,因为程序使用 else if 语句,如果未设置 --age 则仅显示用 --pi 设置的值。
此示例显示帮助:
测试 -h
您可以获得所有命令行选项的完整概述:
Options: -h [ --help ] Help screen --pi arg (=3.1400001) Pi --age arg Age
如您所见,帮助可以以两种不同的方式显示,因为为该命令行选项定义了一个短名称。对于 --pi,显示默认值。命令行选项及其描述会自动格式化。您只需将类型为 boost::program_options::options_description 的对象写入标准输出,如示例 63.1 所示。现在,像这样开始这个例子:测试——年龄输出如下:缺少选项“--age”所需的参数。因为未设置 --age,boost::program_options::parse_command_line() 中使用的解析器会抛出 boost::program_options::error 类型的异常。捕获异常,并将错误消息写入标准输出。boost::program_options::error 派生自 std::logic_error。 Boost.ProgramOptions 定义了额外的异常,它们都派生自 boost::program_options::error。其中一个异常是 boost::program_options::invalid_syntax,如果您没有为 --age 提供值,它就是在示例 63.1 中抛出的异常。示例 63.2 引入了更多可用于 Boost.ProgramOptions 的配置设置。示例 63.2。使用 Boost.ProgramOptions 的特殊配置设置
#include <boost/program_options.hpp>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>using namespace boost::program_options;void to_cout(const std::vector<std::string> &v)
{std::copy(v.begin(), v.end(), std::ostream_iterator<std::string>{std::cout, "\n"});
}int main(int argc, const char *argv[])
{try{int age;options_description desc{"Options"};desc.add_options()("help,h", "Help screen")("pi", value<float>()->implicit_value(3.14f), "Pi")("age", value<int>(&age), "Age")("phone", value<std::vector<std::string>>()->multitoken()->zero_tokens()->composing(), "Phone")("unreg", "Unrecognized options");command_line_parser parser{argc, argv};parser.options(desc).allow_unregistered().style(command_line_style::default_style |command_line_style::allow_slash_for_short);parsed_options parsed_options = parser.run();variables_map vm;store(parsed_options, vm);notify(vm);if (vm.count("help"))std::cout << desc << '\n';else if (vm.count("age"))std::cout << "Age: " << age << '\n';else if (vm.count("phone"))to_cout(vm["phone"].as<std::vector<std::string>>());else if (vm.count("unreg"))to_cout(collect_unrecognized(parsed_options.options,exclude_positional));else if (vm.count("pi"))std::cout << "Pi: " << vm["pi"].as<float>() << '\n';}catch (const error &ex){std::cerr << ex.what() << '\n';}
}
Example 63.2
设置配置后,在解析器上调用 run()。此成员函数在 boost::program_options::parsed_options 类型的对象中返回解析的命令行选项,您可以将其传递给 boost::program_options::store() 以将选项存储在 vm 中。
在代码的后面,示例 63.2 再次访问 vm 以评估命令行选项。只有对 boost::program_options::collect_unrecognized() 的调用是新的。此函数为命令行选项 --unreg 调用。该函数需要一个 boost::program_options::parsed_options 类型的对象,它由 run() 返回。它在 std::vector<std::string> 中返回所有未知的命令行选项。例如,如果您使用 test --unreg --abc 启动程序,--abc 将写入标准输出。
当 boost::program_options::exclude_positional 作为第二个参数传递给 boost::program_options::collect_unrecognized() 时,位置选项将被忽略。对于示例 63.2,这无关紧要,因为没有定义位置选项。但是,boost::program_options::collect_unrecognized() 需要此参数。
示例 63.3 说明了位置选项。
示例 63.3。 Boost.ProgramOptions 的位置选项
#include <boost/program_options.hpp>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>using namespace boost::program_options;void to_cout(const std::vector<std::string> &v)
{std::copy(v.begin(), v.end(),std::ostream_iterator<std::string>{std::cout, "\n"});
}int main(int argc, const char *argv[])
{try{options_description desc{"Options"};desc.add_options()("help,h", "Help screen")("phone", value<std::vector<std::string>>()->multitoken()->zero_tokens()->composing(), "Phone");positional_options_description pos_desc;pos_desc.add("phone", -1);command_line_parser parser{argc, argv};parser.options(desc).positional(pos_desc).allow_unregistered();parsed_options parsed_options = parser.run();variables_map vm;store(parsed_options, vm);notify(vm);if (vm.count("help"))std::cout << desc << '\n';else if (vm.count("phone"))to_cout(vm["phone"].as<std::vector<std::string>>());}catch (const error &ex){std::cerr << ex.what() << '\n';}
}
Example 63.3
示例 63.3 使用类 boost::program_options::positional_options_description 将 --phone 定义为位置选项。此类提供成员函数 add(),它需要传递命令行选项的名称和位置。该示例传递“phone”和 -1。使用位置选项,可以在命令行上设置值,而无需使用命令行选项。您可以像这样启动示例 63.3:测试 123 456即使未使用 --phone,123 和 456 也会被识别为电话号码。在类型为 boost::program_options::positional_options_description 的对象上调用 add() 会将命令行上的值分配给使用位置编号的命令行选项。当使用命令行测试 123 456 调用示例 63.3 时,123 的位置编号为 0,456 的位置编号为 1。示例 63.3 将 -1 传递给 add(),它将所有值 - 123 和 456 - 分配给 - -电话。如果您更改示例 63.3 以将值 0 传递给 add(),则只有 123 会被识别为电话号码。如果将 1 传递给 add(),则只会识别 456。pos_desc 与 positional() 一起传递给解析器。这就是解析器如何知道哪些命令行选项是位置的。请注意,您必须确保定义了位置选项。例如,在示例 63.3 中,“phone”只能传递给 add(),因为 --phone 的定义已经存在于 desc 中。在之前的所有示例中,Boost.ProgramOptions 用于解析命令行选项。但是,该库也支持从文件加载配置选项。如果必须重复设置相同的命令行选项,这会很有用。示例 63.4。从配置文件加载选项
#include <boost/program_options.hpp>
#include <string>
#include <fstream>
#include <iostream>using namespace boost::program_options;int main(int argc, const char *argv[])
{try{options_description generalOptions{"General"};generalOptions.add_options()("help,h", "Help screen")("config", value<std::string>(), "Config file");options_description fileOptions{"File"};fileOptions.add_options()("age", value<int>(), "Age");variables_map vm;store(parse_command_line(argc, argv, generalOptions), vm);if (vm.count("config")){std::ifstream ifs{vm["config"].as<std::string>().c_str()};if (ifs)store(parse_config_file(ifs, fileOptions), vm);}notify(vm);if (vm.count("help"))std::cout << generalOptions << '\n';else if (vm.count("age"))std::cout << "Your age is: " << vm["age"].as<int>() << '\n';}catch (const error &ex){std::cerr << ex.what() << '\n';}
}
Example 63.4
示例 63.4 使用了两个类型为 boost::program_options::options_description 的对象。 generalOptions 定义必须在命令行上设置的选项。 fileOptions 定义可以从配置文件加载的选项。不必使用类型为 boost::program_options::options_description 的两个不同对象来定义选项。如果命令行和文件的选项集相同,则可以只使用一个。在示例 63.4 中,分隔选项是有意义的,因为您不想允许在配置文件中设置 --help。如果允许并且用户将该选项放入配置文件中,则程序每次都会显示帮助屏幕。示例 63.4 从配置文件加载 --age。您可以将配置文件的名称作为命令行选项传递。出于这个原因,在此示例中,--config 是在 generalOptions 中定义的。在使用 boost::program_options::parse_command_line() 解析命令行选项并存储在 vm 中后,该示例检查是否设置了 --config。如果是,则使用 std::ifstream 打开配置文件。 std::ifstream 对象与描述选项的文件选项一起传递给函数 boost::program_options::parse_config_file()。 boost::program_options::parse_config_file() 做与 boost::program_options::parse_command_line() 相同的事情,并在 boost::program_options::parsed_options 类型的对象中返回解析后的选项。该对象被传递给 boost::program_options::store() 以将解析的选项存储在 vm 中。如果您创建一个名为 config.txt 的文件,将 age=29 放入该文件,然后执行下面的命令行,您将得到显示的结果。测试 --config config.txt输出如下:
Your age is: 29
如果您在命令行和配置文件中支持相同的选项,您的程序可能会解析相同的选项两次——一次使用 boost::program_options::parse_command_line(),一次使用 boost::program_options::parse_config_file()。函数调用的顺序决定了您将在 vm 中找到哪个值。一旦命令行选项的值存储在 vm 中,该值将不会被覆盖。该值是由命令行上的选项还是在配置文件中设置的,仅取决于您调用 store() 函数的顺序。
Boost.ProgramOptions 还定义了函数 boost::program_options::parse_environment(),可用于从环境变量加载选项。类 boost::environment_iterator 允许您迭代环境变量。