6

複数のセクションを持つiniファイルを読み取るためにboost::program_optionsを取得しようとしています:

[slave]
address=localhost
port=1111

[slave]
address=192.168.0.1
port=2222

解決策はありますか?

前もって感謝します!

4

1 に答える 1

18

この問題にはいくつかの解決策があります。これは最初は簡単な作業のように見えるかもしれませんが、多くの場合かなり複雑です。これは、セクションが名前空間とほぼ同等であるためです。セクションはオブジェクトと同等ではありません。

【奴隷】
アドレス=ローカルホスト
ポート=1111

【奴隷】
アドレス=192.168.0.1
ポート=2222

上記の構成には、 2 つの値と 2slaveつの値を含む単一の名前空間があります。それぞれがとを持つ 2 つのオブジェクトはありません。この違いにより、値の関連付けまたはペアリングは、アプリケーション コードで行う必要があります。これにより、次のオプションが提示されます。addressportslaveaddressport

  • 構成ファイルのレイアウトを使用して、ペアリングを暗示します。
  • 複数の値を単一のフィールド値にマージして、明示的なペアリングを実行します。

暗黙のペアリング

このアプローチでは、構成ファイルをそのままにしておくことができます。このアプローチの単純さは、次の要素に依存します。

  • いくつかの Boost.ProgramOption コンポーネントの動作。
  • 各オブジェクトは、オプションのフィールドがなく、少数のフィールドを持つセクションとして表されます。
【奴隷】
address=localhost # スレーブ.アドレス[0]
port=1111 # スレーブ.ポート[0]

【奴隷】
address=192.168.0.1 # スレーブ.アドレス[1]
port=2222 # スレーブ.ポート[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

基本的な明示的なペアリング

複数の値を 1 つのフィールド内で意味のある方法で表すことができる場合があります。addressとの両方の一般的な表現の 1 つportaddress:portです。このペアリングにより、結果の構成ファイルは次のようになります。

【奴隷】
スレーブ=localhost: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_descriptionます。optionsslaves.slavestd::vector< std::string >
  • make_slaveは単一のstd::string引数を取り、そこから and を抽出addressportます。
  • 呼び出しを更新して、std::transform1 つの範囲のみを反復処理します。

高度な明示的ペアリング

多くの場合、複数のフィールドを 1 つのキーのない値として意味のある形で表すことができないか、オブジェクトにオプションのフィールドがあります。これらのケースでは、追加レベルの構文と解析が発生する必要があります。アプリケーションは独自の構文とパーサーを導入できますが、Boost.ProgramOption のコマンド ライン構文 (--key valueおよび--key=value) とパーサーを活用することをお勧めします。結果の構成ファイルは次のようになります。

【奴隷】
スレーブ= --アドレス localhost --ポート 1111
スレーブ= --アドレス = 192.168.0.1 --ポート=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_ifC++03 で見落とされていたアルゴリズムだったので作成されました。
  • Boost.Tokenizer は引用符で囲まれたエスケープを簡単に処理できるため、Boost.StringAlgo の代わりに Boost.Tokenizer を使用します。
  • option_builder変換を適用するための慣用的な再利用を提供するために、単項ファンクターを作成しました。
  • make_slaveオブジェクトをboost::program_options::variables_map構築する元となる を受け取るようになりました。slave

このアプローチは、次のバリエーションをサポートするように簡単に拡張することもできます。

  • 単一の値に対して複数のコマンドラインをサポートします。たとえば、1 つの構成で 2 つのスレーブをサポートし、最初のスレーブに障害が発生した場合に備えて、スレーブの 1 つにセカンダリ構成を持たせることができます。,これには、区切り文字で最初のトークン化を実行する必要があります。

    【奴隷】
    スレーブ = --address localhost --port 1111, --address 127.0.0.1 --port 1112
    スレーブ = --アドレス 192.168.0.1 --ポート 2222
  • 引数に与えられた変数でslave_descasのオプションを宣言します。これらの同じ変数は、経由でファクトリ関数にバインドできます。これは Boost.ProgramOptions 型から切り離されますが、多くのフィールドを持つ型の維持が難しくなる可能性があります。typed_valuestore_toboost::refboost::bindmake_slavemake_slave


代替アプローチ

別のアプローチでは、複数の値を 1 つの値に配置することで、明示的なペアリングを行う必要があります。boost::program_options::typed_valueただし、 または のいずれかから継承することにより、解析フェーズ中に変換が発生する可能性がありますboost::program_options::untyped_value

  • から継承する場合は、関数typed_valueをオーバーライドしparseます。を使用した結果の 1 つtyped_valueは、テンプレート パラメータが のすべての要件を満たす必要があるということですtyped_value。たとえば、typed_value< slave >が使用された場合、デフォルトを構築可能にし、 の抽出 ( ) 演算子と挿入 ( ) 演算子のslave両方を定義する必要があります。istream>>ostream<<slave
  • から継承する場合は、と関数untyped_valueの両方をオーバーライドします。このアプローチは、 のような型要件を課しませんが、派生クラスが独自の変数を保持する必要があります。parsenotifytyped_valuestore_to

提案

  • オプションのフィールドが存在せず、フィールドの数が最小限 (2 ~) になることが絶対的に確実な場合は、暗黙のペアリング アプローチを使用します。
  • 最小限のフィールド (2 ~) があり、フィールド名識別子なしで意味のある方法で値を表すことができる場合は、基本的な明示的なペアリングを使用します。オプションのフィールドをサポートできますが、構文とパーサーの両方が複雑になります。
  • 他のすべての場合、または不確実性がある場合は、高度な明示的なペアリングを使用してください。もう少し手間がかかるかもしれませんが、再利用性が向上します。たとえば、スレーブ構成が非常に複雑になり、各スレーブが独自の構成ファイルを持つようになった場合、パーサー タイプと呼び出しのみを変更する必要があるため、コードの変更は最小限で済みます。
于 2012-09-20T13:09:19.040 に答える