【问题标题】:boost::program_options and multiple sections in ini-fileboost::program_options 和 ini 文件中的多个部分
【发布时间】:2012-09-18 10:40:46
【问题描述】:

我正在尝试让 boost::program_options 读取包含多个部分的 ini 文件:

[slave]
address=localhost
port=1111

[slave]
address=192.168.0.1
port=2222

有什么解决办法吗?

提前致谢!

【问题讨论】:

    标签: c++ boost-program-options


    【解决方案1】:

    这个问题有几个解决方案。虽然最初看起来这应该是一项容易的任务,但它通常相当复杂。这是因为节大致相当于命名空间;部分不等同于对象。

    [奴隶]
    地址=本地主机
    端口=1111
    
    [奴隶]
    地址=192.168.0.1
    端口=2222

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

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

    隐含配对

    使用这种方法,配置文件可以保持原样。这种方法的简单性取决于:

    • 一些 Boost.ProgramOption 组件的行为。
    • 每个对象都表示为一个没有可选字段和少量字段的部分。
    [奴隶]
    地址=本地主机#slave.address[0]
    端口=1111 #slave.port[0]
    
    [奴隶]
    地址=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

    基本显式配对

    有时可以在一个字段中以有意义的方式表示多个值。 addressport 的一种常见表示是 address:port。通过这种配对,生成的配置文件如下:

    [奴隶]
    从机=本地主机:1111
    从机=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_descriptionoptions 需要将slaves.slave 读作std::vector&lt; std::string &gt;
    • make_slave 将采用单个 std::string 参数,从中提取 addressport
    • 更新std::transform 调用以仅迭代一个范围。

    高级显式配对

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

    [奴隶]
    从机= --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 对象。

    此方法还可以轻松扩展以支持以下变体:

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

      [奴隶]
      从机 = --address localhost --port 1111, --address 127.0.0.1 --port 1112
      从机 = --address 192.168.0.1 --port 2222
    • slave_desc 的选项声明为typed_value,并将变量提供给store_to 参数。然后这些相同的变量可以通过boost::bindboost::ref 绑定到make_slave 工厂函数。虽然这将 make_slave 与 Boost.ProgramOptions 类型分离,但对于具有许多字段的类型可能会变得难以维护。


    替代方法

    替代方法仍然需要通过将多个值放入单个值来完成显式配对。但是,通过从 boost::program_options::typed_valueboost::program_options::untyped_value 继承,可以在解析阶段发生转换。

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

    建议

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

    【讨论】:

      猜你喜欢
      • 2011-05-29
      • 2013-02-17
      • 2011-11-15
      • 2022-06-30
      • 2023-03-11
      • 2021-01-08
      • 1970-01-01
      • 1970-01-01
      • 2019-01-16
      相关资源
      最近更新 更多