使用带有boost::program_options的多个源时,请使用最后一个存储值,而不是第一个存储值

Use the last rather than the first stored value when using multiple sources with boost::program_options

本文关键字:存储 请使用 第一个 最后一个 boost program options      更新时间:2023-10-16

我的目标是在一个或多个INI文件中保留大部分时间不变的配置值,并从命令行读取其余参数。我正在使用boost::program_options来实现这一点。

基本上,

options_description options;
variables_map vm;
ifstream ifs("config.ini");
store(parse_config_file(ifs, options), vm);
// !!! Does not overwrite values from config.ini
store(command_line_parser(argc, argv).options(options).run(), vm);

只要INI和命令行中提供的参数不重叠,这种方法就可以很好地工作。如果它们重叠,所需的逻辑行为是命令行中的参数覆盖以前从INI文件中读取的任何内容。不过,这并不起作用,因为Boost的默认行为是使用第一个遇到的值,然后忽略任何其他值。

你有什么办法绕过这个限制吗?

我正在做的一个完整的工作示例:

#include <iostream>
#include <fstream>
#include <vector>
#include <boost/program_options.hpp>
using namespace std;
using namespace boost::program_options;
// Parsed values will be stored in these
std::vector<std::string> inputFiles;
int numThreads;
bool enableX;
void parseOptions(const int argc, const char* argv[])
{
//
// Define our config parameters
//
options_description basicOptions("Main options");
basicOptions.add_options()
("help,h", "Show full help.")
("version,V", "Show version.")
("config,c",
value<vector<string>>()->multitoken()->composing(),
"Path of a configuration INI file. Can be used repeatedly.n"
"The available config parameters are listed in the full help info."
)
("inputFile,i",
value<vector<string>>()->multitoken()->composing()->required(),
"Input file(s). Can be used repeatedly."
);
options_description configOptions("Configuration");
configOptions.add(basicOptions);
configOptions.add_options()
("numThreads",
value<>(&numThreads)->default_value(1),
"Number of processing threads."
)
// ... snip ...
("enableX",
value<>(&enableX)->default_value(true),
"Whether to enable X."
);
//
// Parse
//
variables_map vm;
// Parse the minimal set of command line arguments first
parsed_options parsedCL = command_line_parser(argc, argv).
options(basicOptions).allow_unregistered().run();
store(parsedCL, vm);
// Load configuration from INI files
if (vm.count("config"))
{
for (int i = 0; i < vm["config"].as<vector<string>>().size(); i++)
{
ifstream ifs(vm["config"].as<vector<string>>()[i].c_str());
store(parse_config_file(ifs, configOptions), vm);
}
}
// Parse and store the remaining command line arguments
// !!! store() does not overwrite existing values !!!
store(command_line_parser(
collect_unrecognized(parsedCL.options, include_positional)
).options(configOptions).run(), vm);
// Finally, check that that the provided input is valid
notify(vm);
}
int main(const int argc, const char* argv[])
{
try
{
parseOptions(argc, argv);
cout << "enableX: " << enableX << endl;
}
catch (exception e)
{
cerr << e.what() << endl;
}
return 0;
}

如果使用--config config.ini --enableX false运行上述程序,config.ini的内容为

numThreads = 4
enableX = true

它从命令行输出"enableX:1",而不是"enableX:0"——所需的值。

在一次破解尝试中,我尝试在存储任何新参数之前,简单地从variable_map中删除冲突的参数:

void store_with_overwrite(const parsed_options& parsed, variables_map& vm)
{
for (const option& option : parsed.options)
{
auto it = vm.find(option.string_key);
if (it != vm.end())
{
vm.erase(it);
}
}
store(parsed, vm);
}

这不起作用,因为Boost也将第一个遇到的值存储在私有m_final成员变量中,即使参数已从映射中删除,也会使用该变量。

我现在能想到的唯一解决方案是首先解析命令行参数,然后在store()处理INI文件之前,从INI文件中删除parsed_options中任何冲突的值。缺点是,我必须手动跟踪使用multitoken()composing()的选项,这些选项不能删除。

必须有更好的方法。你有什么建议吗?

由于第一个存储的值是最后一个保持不变的值,这也是我想要的命令行值,因此解决方案实际上很简单:首先从命令行解析和存储所有内容。

通常,将来自不同来源的值以与您希望它们相互"覆盖"的顺序相反的顺序存储。

variables_map vm;
// Parse the command line arguments first
parsed_options parsedCL = command_line_parser(argc, argv).
options(configOptions).run();
store(parsedCL, vm);
// Load configuration from INI files
if (vm.count("config"))
{
for (int i = 0; i < vm["config"].as<vector<string>>().size(); i++)
{
ifstream ifs(vm["config"].as<vector<string>>()[i].c_str());
store(parse_config_file(ifs, configOptions), vm);
}
}
// Finally, check that that the provided input is valid
notify(vm);