複数のセクションを持つiniファイルを読み取るためにboost::program_optionsを取得しようとしています:
[slave]
address=localhost
port=1111
[slave]
address=192.168.0.1
port=2222
解決策はありますか?
前もって感謝します!
複数のセクションを持つiniファイルを読み取るためにboost::program_optionsを取得しようとしています:
[slave]
address=localhost
port=1111
[slave]
address=192.168.0.1
port=2222
解決策はありますか?
前もって感謝します!
この問題にはいくつかの解決策があります。これは最初は簡単な作業のように見えるかもしれませんが、多くの場合かなり複雑です。これは、セクションが名前空間とほぼ同等であるためです。セクションはオブジェクトと同等ではありません。
【奴隷】 アドレス=ローカルホスト ポート=1111 【奴隷】 アドレス=192.168.0.1 ポート=2222
上記の構成には、 2 つの値と 2slave
つの値を含む単一の名前空間があります。それぞれがとを持つ 2 つのオブジェクトはありません。この違いにより、値の関連付けまたはペアリングは、アプリケーション コードで行う必要があります。これにより、次のオプションが提示されます。address
port
slave
address
port
このアプローチでは、構成ファイルをそのままにしておくことができます。このアプローチの単純さは、次の要素に依存します。
【奴隷】 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 つport
はaddress: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
ます。options
slaves.slave
std::vector< std::string >
make_slave
は単一のstd::string
引数を取り、そこから and を抽出address
しport
ます。std::transform
1 つの範囲のみを反復処理します。多くの場合、複数のフィールドを 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_if
C++03 で見落とされていたアルゴリズムだったので作成されました。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_desc
asのオプションを宣言します。これらの同じ変数は、経由でファクトリ関数にバインドできます。これは Boost.ProgramOptions 型から切り離されますが、多くのフィールドを持つ型の維持が難しくなる可能性があります。typed_value
store_to
boost::ref
boost::bind
make_slave
make_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
の両方をオーバーライドします。このアプローチは、 のような型要件を課しませんが、派生クラスが独自の変数を保持する必要があります。parse
notify
typed_value
store_to