boost::program_options和ini文件中的多个部分

6
我正在尝试使用boost::program_options读取具有多个节的ini文件:
[slave]
address=localhost
port=1111

[slave]
address=192.168.0.1
port=2222

有什么解决方案吗?

提前感谢!

1个回答

18

这个问题有几种解决方案。尽管最初看起来这应该是一个简单的任务,但实际上它通常相当复杂。这是因为节(sections)大致等同于命名空间;节不等同于对象。

[slave]
address=localhost
port=1111
[slave] address=192.168.0.1 port=2222

以上配置有一个单独的slave命名空间,其中包含两个address值和两个port值。没有两个slave对象,每个对象都有一个addressport。由于这个区别,关联值或配对必须在应用程序代码中完成。这提供以下选项:

  • 使用配置文件的布局暗示配对。
  • 通过将多个值合并为单个字段值来执行显式配对。

暗示配对

采用这种方法,配置文件可以保持不变。这种方法的简单性取决于:

  • 一些 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]

不修改配置,以下代码:

#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" ) );
}

产生以下输出:

从机地址:localhost,端口:1111
从机地址:192.168.0.1,端口:2222

基本显式配对

在有意义的情况下,可以偶尔将多个值表示为单个字段。其中一个常见的表示方式是 address:port。使用这种配对方法,生成的配置文件如下:

[slaves]
slave=localhost:1111
slave=192.168.0.1:2222

这种方法的简单性取决于:

  • 能够将多个值表示为单个有意义的值而不需要键说明符。
  • 每个对象没有可选值。

更新后的代码:

#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" ) );
}

产生相同的输出:

从属地址:localhost,端口:1111
从属地址:192.168.0.1,端口:2222

值得注意的代码修改如下:

  • options_description中的options需要将slaves.slave读取为一个std::vector< std::string >
  • make_slave将使用单个std::string参数,从中提取addressport
  • 更新std::transform调用,仅迭代一个范围。

高级显示配对

通常,多个字段不能有意义地表示为单个无键值,或者对象具有可选字段。 对于这些情况,需要进行额外级别的语法和解析。虽然应用程序可以引入自己的语法和解析器,但我建议利用Boost.ProgramOption的命令行语法(--key value--key=value)和解析器。 结果配置文件可能看起来像:

[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" ) ); 
}

与之前的方法产生相同的输出:

从属地址:localhost,端口:1111
从属地址:192.168.0.1,端口:2222

显著的代码修改如下:

  • 创建了copy_if,因为它是C++03中被忽视的算法。
  • 使用Boost.Tokenizer代替Boost.StringAlgo,因为Boost.Tokenizer更容易处理带引号的转义字符。
  • 创建了一个option_builder一元函数对象,以帮助提供应用变换的惯用复用。
  • make_slave现在接受一个boost::program_options::variables_map,从中构造一个slave对象。

这种方法也可以很容易地扩展到支持以下变化:

  • 支持单个值的多个命令行。例如,配置可以支持两个从属,其中一个从属具有备用配置,以防第一个失败。这需要对,分隔符进行初始标记化。

    [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::bind将这些相同的变量绑定到make_slave工厂函数中的boost::ref。虽然这将make_slave与Boost.ProgramOptions类型解耦,但对于具有许多字段的类型来说,可能难以维护。


替代方法

替代方法仍需要通过将多个值放入单个值中来进行显式配对。但是,在解析阶段可以通过继承boost::program_options::typed_valueboost::program_options::untyped_value来进行变换。

  • 继承自typed_value时,需要重写parse函数。使用typed_value的一个后果是模板参数必须满足typed_value的所有要求。例如,如果使用了typed_value<slave>,则需要使slave具有默认构造功能,并为slave定义istream提取(>>)和ostream插入(<<)运算符。
  • 继承自untyped_value时,需要同时重写parsenotify函数。这种方法不像typed_value那样强制要求类型,但它确实要求派生类维护自己的store_to变量。

建议

  • 当绝对确定不会有可选字段并且字段数量很少(2~)时,请使用隐含配对方法。
  • 如果将有最少量的字段(2~)并且值可以在没有字段名称标识的情况下以有意义的方式表示,则使用基本显式配对。可以支持可选字段,但它增加了语法和解析器的复杂性。
  • 对于所有其他情况,或者存在任何不确定性时,请使用高级显式配对。虽然可能需要更多的工作,但它提供了更大的可重用性。例如,如果从属配置变得非常复杂,以至于每个从属都有自己的配置文件,则只需进行少量代码更改,仅需更改解析器类型和调用即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接