问题描述
我想要获得boost :: program_options来读取一个包含多个部分的ini文件:
I'm trying to get boost::program_options to read a ini file with multiple sections:
[slave]
address=localhost
port=1111
[slave]
address=192.168.0.1
port=2222
有任何解决方案吗?
提前感谢!
推荐答案
这个问题有几个解决方案。虽然它最初可能看起来这应该是一个容易的任务,它往往相当参与。这是因为段大致相当于命名空间;部分不等同于对象。
There are a few solutions to this problem. While it may initially appear that this should be an easy task, it is often fairly involved. This is because sections are roughly equivalent to namespaces; sections are not equivalent to objects.
[slave]
address=localhost
port=1111
[slave]
address=192.168.0.1
port=2222
上述配置有一个 slave
命名空间。没有两个地址
值和两个 port
值的 slave
对象,每个都有一个地址
和 port
。由于这种区别,必须在应用程序代码中完成关联值或配对。其中包含以下选项:
The above configuration has a single slave
namespace, that contains two address
values and two port
values. There are not two slave
objects that each have an address
and port
. Due to this distinction, associating values, or pairing, must be done in the application code. This presenting the following options:
- 使用配置文件的布局来表示配对。
使用这种方法,配置文件可以保持原样。此方法的简单性取决于:
With this approach, the configuration file can remain as-is. The simplicity of this approach depends on:
- 几个Boost.ProgramOption组件的行为。
- 每个对象表示为没有可选字段和少量字段的部分。
[slave]
address=localhost # slave.address[0]
port=1111 # slave.port[0]
[slave]
address=192.168.0.1 # slave.address[1]
port=2222 # slave.port[1]
不修改配置,以下代码:
Without modifying the configuration, the following code:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
/// @brief Convenience function for when a 'store_to' value is being provided
/// to typed_value.
///
/// @param store_to The variable that will hold the parsed value upon notify.
///
/// @return Pointer to a type_value.
template < typename T >
boost::program_options::typed_value< T >* make_value( T* store_to )
{
return boost::program_options::value< T >( store_to );
}
/// @brief Slave type that contains an address and port.
struct slave
{
std::string address;
unsigned short port;
/// @brief Constructor.
slave( std::string address,
unsigned short port )
: address( address ),
port( port )
{}
};
/// @brief Stream insertion operator for slave.
///
/// @param stream The stream into which slave is being inserted.
/// @param s The slave object.
///
/// @return Reference to the ostream.
std::ostream& operator<<( std::ostream& stream,
const slave& slave )
{
return stream << "Slave address: " << slave.address
<< ", port: " << slave.port;
}
/// @brief Makes a slave given an address and port.
slave make_slave( const std::string& address,
unsigned short port )
{
return slave( address, port );
}
int main()
{
// Variables that will store parsed values.
std::vector< std::string > addresses;
std::vector< unsigned short > ports;
// Setup options.
namespace po = boost::program_options;
po::options_description desc( "Options" );
desc.add_options()
( "slave.address", make_value( &addresses ),
"slave's hostname or ip address" )
( "slave.port" , make_value( &ports ),
"plugin id" );
// Load setting file.
po::variables_map vm;
std::ifstream settings_file( "config.ini", std::ifstream::in );
po::store( po::parse_config_file( settings_file , desc ), vm );
settings_file.close();
po::notify( vm );
// Transform each address and port pair into a slave via make_slave,
// inserting each object into the slaves vector.
std::vector< slave > slaves;
std::transform( addresses.begin(), addresses.end(),
ports.begin(),
std::back_inserter( slaves ),
make_slave );
// Print slaves.
std::copy( slaves.begin(), slaves.end(),
std::ostream_iterator< slave >( std::cout, "\n" ) );
}
产生此输出:
Slave address: localhost, port: 1111
Slave address: 192.168.0.1, port: 2222
基本显式配对
多个值可以偶尔以有意义的方式在单个字段中表示。 address
和 port
的一个常见表示是 address:port
。使用此配对,生成的配置文件将是:
Basic Explicit Pairing
Multiple values can be occasionally be represented within a single field in a meaningful way. One common representation of both address
and port
is address:port
. With this pairing, the resulting configuration file would like:
[slaves]
slave=localhost:1111
slave=192.168.0.1:2222
这种方法的简单性取决于:
This simplicity of this approach depends upon:
- 每个对象没有可选值。
更新的代码:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
/// @brief Convenience function for when a 'store_to' value is being provided
/// to typed_value.
///
/// @param store_to The variable that will hold the parsed value upon notify.
///
/// @return Pointer to a type_value.
template < typename T >
boost::program_options::typed_value< T >* make_value( T* store_to )
{
return boost::program_options::value< T >( store_to );
}
/// @brief Slave type that contains an address and port.
struct slave
{
std::string address;
unsigned short port;
/// @brief Constructor.
slave( std::string address,
unsigned short port )
: address( address ),
port( port )
{}
};
/// @brief Stream insertion operator for slave.
///
/// @param stream The stream into which slave is being inserted.
/// @param s The slave object.
///
/// @return Reference to the ostream.
std::ostream& operator<<( std::ostream& stream,
const slave& slave )
{
return stream << "Slave address: " << slave.address
<< ", port: " << slave.port;
}
/// @brief Makes a slave given an address and port.
slave make_slave( const std::string& address_and_port )
{
// Tokenize the string on the ":" delimiter.
std::vector< std::string > tokens;
boost::split( tokens, address_and_port, boost::is_any_of( ":" ) );
// If the split did not result in exactly 2 tokens, then the value
// is formatted wrong.
if ( 2 != tokens.size() )
{
using boost::program_options::validation_error;
throw validation_error( validation_error::invalid_option_value,
"slaves.slave",
address_and_port );
}
// Create a slave from the token values.
return slave( tokens[0],
boost::lexical_cast< unsigned short >( tokens[1] ) );
}
int main()
{
// Variables that will store parsed values.
std::vector< std::string > slave_configs;
// Setup options.
namespace po = boost::program_options;
po::options_description desc( "Options" );
desc.add_options()
( "slaves.slave", make_value( &slave_configs ),
"slave's address@port" );
// Load setting file.
po::variables_map vm;
std::ifstream settings_file( "config.ini", std::ifstream::in );
po::store( po::parse_config_file( settings_file , desc ), vm );
settings_file.close();
po::notify( vm );
// Transform each config into a slave via make_slave, inserting each
// object into the slaves vector.
std::vector< slave > slaves;
std::transform( slave_configs.begin(), slave_configs.end(),
std::back_inserter( slaves ),
make_slave );
// Print slaves.
std::copy( slaves.begin(), slaves.end(),
std::ostream_iterator< slave >( std::cout, "\n" ) );
}
产生相同的输出:
Slave address: localhost, port: 1111
Slave address: 192.168.0.1, port: 2222
代码修改如下:
-
options_description
的options
需要读取slaves.slave
作为std :: vector< std :: string>
。 -
make_slave
将使用单个std :: string
参数,从中提取地址
和port
。 - 更新
std :: transform
调用只在一个范围内迭代。
- The
options_description
'soptions
need to be readingslaves.slave
as astd::vector< std::string >
. make_slave
will take a singlestd::string
argument, from which it will extractaddress
andport
.- Update the
std::transform
call to only iterate over one range.
通常,多个字段无法有效地表示为单个键 - 较小的值,或对象具有可选字段。对于这些情况,需要进行额外级别的语法和解析。虽然应用程序可以引入自己的语法和解析器,我建议利用Boost.ProgramOption的命令行语法( - 键值
和 - key = value
)和解析器。生成的配置文件可能如下所示:
Often, multiple fields cannot be represented meaningfully as a single key-less value, or an object has optional fields. For these cases, an additional level of syntax and parsing needs to occur. While applications can introduce their own syntax and parsers, I suggest leveraging Boost.ProgramOption's command line syntax (--key value
and --key=value
) and parsers. The resulting configuration file could look like:
[slaves]
slave= --address localhost --port 1111
slave= --address = 192.168.0.1 --port=2222
更新的代码:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/bind.hpp>
#include <boost/program_options.hpp>
#include <boost/tokenizer.hpp>
// copy_if was accidently left out of the C++03 standard, so mimic the
// C++11 behavior to support all predicate types. The alternative is to
// use remove_copy_if, but it only works for adaptable functors.
template < typename InputIterator,
typename OutputIterator,
typename Predicate >
OutputIterator
copy_if( InputIterator first,
InputIterator last,
OutputIterator result,
Predicate pred )
{
while( first != last )
{
if( pred( *first ) )
*result++ = *first;
++first;
}
return result;
}
/// @brief Tokenize a string. The tokens will be separated by each non-quoted
/// character in @c separator_characters. Empty tokens are removed.
///
/// @param input The string to tokenize.
/// @param separator_characters The characters on which to delimit.
///
/// @return Vector of tokens.
std::vector< std::string > tokenize( const std::string& input,
const std::string& separator_characters )
{
typedef boost::escaped_list_separator< char > separator_type;
separator_type separator( "\\", // The escape characters.
separator_characters,
"\"\'" ); // The quote characters.
// Tokenize the intput.
boost::tokenizer< separator_type > tokens( input, separator );
// Copy non-empty tokens from the tokenizer into the result.
std::vector< std::string > result;
copy_if( tokens.begin(), tokens.end(), std::back_inserter( result ),
!boost::bind( &std::string::empty, _1 ) );
return result;
}
/// @brief option_builder provides a unary operator that can be used within
/// stl::algorithms.
template < typename ResultType,
typename Builder >
class option_builder
{
public:
typedef ResultType result_type;
public:
/// @brief Constructor
option_builder( const boost::program_options::options_description& options,
Builder builder )
: options_( options ),
builder_( builder )
{}
/// @brief Unary operator that will parse @c value, then delegate the
/// construction of @c result_type to the builder.
template < typename T >
result_type operator()( const T& value )
{
// Tokenize the value so that the command line parser can be used.
std::vector< std::string > tokens = tokenize( value, "= " );
// Parse the tokens.
namespace po = boost::program_options;
po::variables_map vm;
po::store( po::command_line_parser( tokens ).options( options_ ).run(),
vm );
po::notify( vm );
// Delegate object construction to the builder.
return builder_( vm );
}
private:
const boost::program_options::options_description& options_;
Builder builder_;
};
/// @brief Convenience function used to create option_builder types.
template < typename T,
typename Builder >
option_builder< T, Builder > make_option_builder(
const boost::program_options::options_description& options,
Builder builder )
{
return option_builder< T, Builder >( options, builder );
}
/// @brief Convenience function for when a 'store_to' value is being provided
/// to typed_value.
///
/// @param store_to The variable that will hold the parsed value upon notify.
///
/// @return Pointer to a type_value.
template < typename T >
boost::program_options::typed_value< T >* make_value( T* store_to )
{
return boost::program_options::value< T >( store_to );
}
/// @brief Slave type that contains an address and port.
struct slave
{
std::string address;
unsigned short port;
/// @brief Constructor.
slave( std::string address,
unsigned short port )
: address( address ),
port( port )
{}
};
/// @brief Stream insertion operator for slave.
///
/// @param stream The stream into which slave is being inserted.
/// @param s The slave object.
///
/// @return Reference to the ostream.
std::ostream& operator<<( std::ostream& stream,
const slave& slave )
{
return stream << "Slave address: " << slave.address
<< ", port: " << slave.port;
}
/// @brief Makes a slave given an address and port.
slave make_slave( const boost::program_options::variables_map& vm )
{
// Create a slave from the variable map.
return slave( vm["address"].as< std::string >(),
vm["port"].as< unsigned short >() );
}
int main()
{
// Variables that will store parsed values.
std::vector< std::string > slave_configs;
// Setup options.
namespace po = boost::program_options;
po::options_description desc( "Options" );
desc.add_options()
( "slaves.slave", make_value( &slave_configs ),
"slave's --address ip/hostname --port num" );
// Load setting file.
po::variables_map vm;
std::ifstream settings_file( "config.ini", std::ifstream::in );
po::store( po::parse_config_file( settings_file , desc ), vm );
settings_file.close();
po::notify( vm );
// Create options for slaves.slave.
po::options_description slave_desc( "Slave Options" );
slave_desc.add_options()
( "address", po::value< std::string >(),
"slave's hostname or ip address" )
( "port" , po::value< unsigned short >(),
"slave's port" );
// Transform each config into a slave via creating an option_builder that
// will use the slave_desc and make_slave to create slave objects. These
// objects will be inserted into the slaves vector.
std::vector< slave > slaves;
std::transform( slave_configs.begin(), slave_configs.end(),
std::back_inserter( slaves ),
make_option_builder< slave >( slave_desc, make_slave ) );
// Print slaves.
std::copy( slaves.begin(), slaves.end(),
std::ostream_iterator< slave >( std::cout, "\n" ) );
}
产生与以前方法相同的输出:
Produces the same output as the previous approaches:
Slave address: localhost, port: 1111
Slave address: 192.168.0.1, port: 2222
并且显着的代码修改如下:
And the notable code modifications are as follows:
- 创建
copy_if
- 使用Boost.Tokenizer而不是Boost.StringAlgo,因为Boost.Tokenizer会处理引用的转义更容易。
-
option_builder
一元函子,以帮助提供惯用的重用以应用转换。 -
make_slave
现在需要一个boost :: program_options :: variables_map
,它将构造一个slave
对象。
- Created
copy_if
since it was an overlooked algorithm in C++03. - Using Boost.Tokenizer instead of Boost.StringAlgo since Boost.Tokenizer handles quoted escapes easier.
- Created a
option_builder
unary functor to help provide idiomatic reuse for applying transformations. make_slave
now takes aboost::program_options::variables_map
from which it will construct aslave
object.
此方法也可以轻松地扩展为支持以下变体:
This approach can also easily be extended to support the following variations:
-
为单个值支持多个命令行。例如,配置可以支持两个从设备,其中一个从设备在第一个设备故障的情况下具有次级配置。这需要在
,
分隔符上执行初始标记。
Supporting multiple command-lines for a single value. For example, a configuration could support two slaves, with one of the slaves having a secondary configuration in case the first fails. This requires performing an initial tokenization on the
,
delimiter.
[slaves]
slave = --address localhost --port 1111, --address 127.0.0.1 --port 1112
slave = --address 192.168.0.1 --port 2222
使用提供给 slave_desc 的选项为 typed_value
> store_to 参数。这些相同的变量然后可以通过 boost :: ref
通过 boost :: bind
绑定到 make_slave
工厂函数。虽然这会从Boost.ProgramOptions类型中解耦 make_slave
,但对于具有多个字段的类型,可能会变得很难维护。
Declaring the options for slave_desc
as typed_value
with variables provided to the store_to
argument. These same variables can then be bound with boost::ref
via boost::bind
to the make_slave
factory function. While this decouples make_slave
from Boost.ProgramOptions types, it may become difficult to maintain for types with many fields.
替代方法仍需要显式配对,将多个值放入单个值。但是,在解析阶段,可以通过继承 boost :: program_options :: typed_value
或 boost :: program_options :: untyped_value
。
Alternative approaches still need explicit pairing to be done via placing multiple values into a single value. However, transformations can occur during the parsing phase by inheriting from either boost::program_options::typed_value
or boost::program_options::untyped_value
.
- 继承
typed_value
时,覆盖parse
函数。使用typed_value
的一个结果是模板参数必须满足typed_value
的所有要求。例如,如果typed_value< slave>
,则需要使slave
默认可构造,并定义istream
提取(>
)和ostream
插入(< / code>
- 继承
untyped_value
覆盖parse
和notify
函数。这种方法不强加类型要求,例如typed_value
,但它要求派生类保持自己的store_to
变量。
- When inheriting from
typed_value
, override theparse
function. One consequence of usingtyped_value
is that the template parameter must meet all the requirements fortyped_value
. For example, iftyped_value< slave >
was used, then it would require makingslave
default constructable, and defining bothistream
extraction (>>
) andostream
insertion (<<
) operators forslave
. - When inheriting from
untyped_value
, override both theparse
andnotify
functions. This approach does not impose type requirements liketyped_value
, but it does require that the derived class maintain its ownstore_to
variable.
- 当绝对确定永远不会有可选字段,字段数量将是最小(2〜),则使用隐含的配对方法。
- 如果将存在最小量的字段(2〜),并且可以以没有字段名称标识符的有意义的方式表示值,则使用基本显式配对。可以支持可选字段,但会增加语法和解析器的复杂性。
- 对于所有其他情况,或有不确定性时,请使用高级显式配对。虽然它可能需要一些更多的工作,它提供更大的可重用性。例如,如果从配置变得如此复杂,以致每个从设备都有自己的配置文件,那么代码更改是最小的,因为只需要更改解析器类型和调用。
这篇关于boost :: program_options和ini-file中的多个节的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!