在 boost::program_options 中使用多个来源时,使用最后一个而不是第一个存储值
Use the last rather than the first stored value when using multiple sources with boost::program_options
我的目标是将大部分时间不会更改的配置值保存在一个或多个 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()
-ing 之前从 INI 文件中删除 parsed_options
中的任何冲突值。缺点是我必须手动跟踪使用 multitoken()
和 composing()
的选项,不能将其删除。
必须有更好的方法。你有什么建议吗?
由于第一个存储的值是最后的值,这也是我希望命令行的值,解决方案实际上很简单:首先从命令行解析和存储所有内容。
一般来说,以与您希望它们 "overwrite" 彼此相反的顺序相反的顺序存储来自不同来源的值。
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);
我的目标是将大部分时间不会更改的配置值保存在一个或多个 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()
-ing 之前从 INI 文件中删除 parsed_options
中的任何冲突值。缺点是我必须手动跟踪使用 multitoken()
和 composing()
的选项,不能将其删除。
必须有更好的方法。你有什么建议吗?
由于第一个存储的值是最后的值,这也是我希望命令行的值,解决方案实际上很简单:首先从命令行解析和存储所有内容。
一般来说,以与您希望它们 "overwrite" 彼此相反的顺序相反的顺序存储来自不同来源的值。
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);