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

2023-12-31

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

[slave]
address=localhost
port=1111

[slave]
address=192.168.0.1
port=2222

有什么解决办法吗?

提前致谢!


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


[slave]
address=localhost
port=1111

[slave]
address=192.168.0.1
port=2222  

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

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

隐含配对

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

  • 一些 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" ) );
}

产生以下输出:


Slave address: localhost, port: 1111
Slave address: 192.168.0.1, port: 2222  

基本显式配对

有时可以在单个字段中以有意义的方式表示多个值。两者的一种共同表示address and port is 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" ) );
}

产生相同的输出:


Slave address: localhost, port: 1111
Slave address: 192.168.0.1, port: 2222  

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

  • The options_description's options需要读书slaves.slave as a std::vector< std::string >.
  • make_slave将采取单一std::string参数,从中提取address and port.
  • 更新std::transform调用仅迭代一个范围。

高级显式配对

通常,多个字段无法有意义地表示为单个无键值,或者对象具有可选字段。对于这些情况,需要进行额外级别的语法和解析。虽然应用程序可以引入自己的语法和解析器,但我建议利用 Boost.ProgramOption 的命令行语法(--key value and --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" ) ); 
}

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


Slave address: localhost, port: 1111
Slave address: 192.168.0.1, port: 2222  

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

  • Created copy_if因为它是 C++03 中一个被忽视的算法。
  • 使用 Boost.Tokenizer 而不是 Boost.StringAlgo,因为 Boost.Tokenizer 可以更轻松地处理引用转义。
  • 创建了一个option_builder一元函子有助于为应用转换提供惯用的重用。
  • make_slave现在需要一个boost::program_options::variables_map它将从中构建一个slave object.

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

  • 支持单个值的多个命令行。例如,一个配置可以支持两个从属设备,其中一个从属设备具有辅助配置,以防第一个从属设备发生故障。这需要对,分隔符。

    
    [slaves]
    slave = --address localhost --port 1111, --address 127.0.0.1 --port 1112
    slave = --address 192.168.0.1 --port 2222  
  • 声明选项slave_desc as typed_value与提供给变量store_to争论。然后可以将这些相同的变量绑定boost::ref via boost::bind to the make_slave工厂功能。虽然这解耦了make_slave从 Boost.ProgramOptions 类型来看,对于具有许多字段的类型来说,维护可能会变得困难。


替代方法

替代方法仍然需要通过将多个值放入单个值来完成显式配对。但是,在解析阶段可以通过继承任一对象来进行转换boost::program_options::typed_value or boost::program_options::untyped_value.

  • 当继承自typed_value,覆盖parse功能。使用的后果之一typed_value是模板参数必须满足所有要求typed_value。例如,如果typed_value< slave >被使用,那么它需要制作slave默认可构造,并定义两者istream提取(>>) and ostream插入(<<) 运算符slave.
  • 当继承自untyped_value,覆盖两者parse and notify功能。这种方法不会强加类型要求,例如typed_value,但它确实要求派生类维护自己的store_to多变的。

建议

  • 当绝对确定永远不会有可选字段并且字段数量将最少(2~)时,则使用隐含配对方法。
  • 如果存在最少量的字段 (2~),并且可以在没有字段名称标识符的情况下以有意义的方式表示值,则使用基本显式配对。可以支持可选字段,但它增加了语法和解析器的复杂性。
  • 对于所有其他情况或存在任何不确定性时,请使用高级显式配对。虽然这可能需要更多的工作,但它提供了更好的可重用性。例如,如果从属配置变得非常复杂,以至于每个从属都有自己的配置文件,那么代码更改很小,因为只需要更改解析器类型和调用。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 如何终止 pthread 中的休眠线程?

    我有一个线程休眠很长时间 然后醒来做某事 然后再次休眠 如下所示 while some condition do something sleep 1000 我怎样才能让这个线程优雅而快速地退出 我尝试使用pthread cancel 但无法
  • 将 Xlsm 文件另存为 xlsx

    我想将当前的xlsm文件保存为xlsx 所以我编写了如下代码 这段代码确实完成了它的工作 我能够看到保存为 Myfile xlsx 的文件 正如我在代码中定义的那样 但是存在一些小问题 vba 总是将文件保存到另一个文件作为 bookx x
  • 测试 R Shiny 中一组编号输入对象中的任何输入是否为空

    假设我创建了 10 个selectInput多图导出的下拉菜单以及这些selectInputs称为 xaxis 1 xaxis 2 xaxis 10 对于单个 1 我可以写 if is null input xaxis 1 do stuff
  • 了解 numpy 数组中 any() 和 all() 的使用

    以下内容有什么区别 a np array 2 3 4 b np array 2 7 8 if a any b all print yes and a np array 2 3 4 b np array 2 7 8 if a any b an
  • 无法创建 Android 虚拟设备,“没有为此目标安装系统映像”

    无法创建 Android 虚拟设备 https stackoverflow com questions 13488419 unable to create android virtual device 13488547 13488547我点
  • 禁用 jQuery DataTables 中特定列的排序

    我正在使用 jQuery数据表插件 https www datatables net 对表字段进行排序 我的问题是 如何禁用特定列的排序 我已尝试使用以下代码 但它不起作用 aoColumns bSearchable false null
  • Python 错误:TypeError:“时间戳”类型的对象不可 JSON 序列化”

    我有一个 Dataframe 其时间戳列类型为 datetime64 ns 当我尝试将其插入 Salesforce 平台时出现错误 类型错误 时间戳 类型的对象不可 JSON 序列化 我如何更改此时间戳列以使其正确更新 下面给出的是数据框的
  • 在 C# 中,是否可以使用具有不同名称的成员来实现接口成员,就像在 VB.NET 中那样?

    好吧 这是我要问的问题 不是为了展示良好的编码实践 这实际上可以被认为是一种不好的实践 而是为了 可以 完成它 也就是说 在 VB NET 中 您可以实现这样的接口 Sub SomeInterfaceMember Implements IS
  • 使用 Api.ai 从 Google Actions 请求用户位置

    Google Actions 可以为您提供用户的位置 姓名和其他一些详细信息 如果没有 Nodejs SDK 如何在 Api ai 上完成此操作 Google 的所有示例都使用 Nodejs sdk 根据对话API https develo
  • 在没有模块级变量的情况下使功能区控制无效

    我开发了一个包含自定义功能区的 Excel 加载项 我希望能够在某些情况下使功能区上的控件无效 启用 禁用 但我能找到的每个示例都使用模块级或全局变量在首次加载功能区时存储功能区对象 这似乎是一个好方法 但是 此处列出 https stac
  • 如何用 CSS 编写媒体查询?

    我需要在以下情况下编写不同的样式 设备宽度大于设备高度 Landscape media screen and orientation landscape bg img height auto width 100 设备高度大于设备宽度 Por
  • PHP 致命错误:[ionCube Loader] Loader 必须显示为 php.ini 中的第一个条目

    当我尝试启动 Apache 时 Apache 错误日志中出现以下错误 PHP 致命错误 ionCube Loader Loader 必须出现在第一个 php ini 文件中第 0 行未知的条目 从错误消息本身就可以清楚地看出错误是什么 在
  • Android 上有没有办法与 USB 设备通信?

    我有一个非常简短的问题 是否可以在 Android 操作系统上通信 使用 USB 设备 我认为它可能是平板设备 免得说我想将某种 USB 读卡器连接到 Android 平板电脑 它会起作用吗 需要自己写驱动吗 Thanks 首先 您的平板电
  • pd.DataFrame.select_dtypes() 包括 timedelta dtype

    为什么此测试代码是预期行为 test pd DataFrame bool False True int 1 2 float 2 5 3 4 compl np array 1 1j 5 dt pd Timestamp 2013 01 02 p
  • 在 ECLIPSE java 中使用外部 jar (JXL) 创建 jar 可执行文件

    我找不到适用于我的 jar 可执行文件的工作解决方案 该程序在我的 Eclipse IDE 中运行完美 但是当我尝试遵循 Eclipse 指令时 可执行文件无法与外部 JAR 一起使用 JXL jar 位于我的构建路径中 有人知道如何通过
  • 使用单个堆栈生成排列

    任何人都可以解释一下在仅使用单个堆栈时生成可能的排列的算法 并且推入和弹出是唯一允许的操作 查了很多资料 但没有明确的答案 这种排列的总数也由加泰罗尼亚数字给出 但我没能得到这方面的证据 如果可能的话 也请解释一下 Thanks 该问题使用
  • Angular:工厂模型中的更新未反映在控制器中

    我有一个保存用户偏好值的用户偏好工厂 当页面加载时 它是空的 用户登录后 它会填充用户配置文件 伪代码 app factory pref function rootScope var pref age name rootScope on l
  • GNU make -j 选项

    自从我了解 j 以来 我就一直愉快地使用 j8 有一天 我正在编译 atlas 安装 但 make 失败了 最终我追踪到事情是无序的 一旦我回到单线程制作 它就工作得很好 这让我很紧张 在编写自己的 make 文件时 我需要注意哪些条件以避
  • C# Getter/Setter 问题

    假设我在一个类中有一个属性 Vector3 position get set 所以我在某个地方创建了该类的一个实例 现在我想更改position x 但现在这是不可能的 因为 getter 和 setter 设置并获取整个对象 所以我必须让
  • boost::program_options 和 ini 文件中的多个部分

    我试图让 boost program options 读取包含多个部分的 ini 文件 slave address localhost port 1111 slave address 192 168 0 1 port 2222 有什么解决办