boost :: program_options和ini-file中的多个节 [英] boost::program_options and multiple sections in ini-file

查看:202
本文介绍了boost :: program_options和ini-file中的多个节的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要获得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's options need to be reading slaves.slave as a std::vector< std::string >.
  • make_slave will take a single std::string argument, from which it will extract address and port.
  • 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 a boost::program_options::variables_map from which it will construct a slave 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 the parse function. One consequence of using typed_value is that the template parameter must meet all the requirements for typed_value. For example, if typed_value< slave > was used, then it would require making slave default constructable, and defining both istream extraction (>>) and ostream insertion (<<) operators for slave.
    • When inheriting from untyped_value, override both the parse and notify functions. This approach does not impose type requirements like typed_value, but it does require that the derived class maintain its own store_to variable.

    • 当绝对确定永远不会有可选字段,字段数量将是最小(2〜),则使用隐含的配对方法。

    • 如果将存在最小量的字段(2〜),并且可以以没有字段名称标识符的有意义的方式表示值,则使用基本显式配对。可以支持可选字段,但会增加语法和解析器的复杂性。

    • 对于所有其他情况,或有不确定性时,请使用高级显式配对。虽然它可能需要一些更多的工作,它提供更大的可重用性。例如,如果从配置变得如此复杂,以致每个从设备都有自己的配置文件,那么代码更改是最小的,因为只需要更改解析器类型和调用。

    这篇关于boost :: program_options和ini-file中的多个节的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    相关文章
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆