kanetaiの二次記憶装置

プログラミングに関するやってみた、調べた系のものをQitaに移して、それ以外をはてブでやる運用にしようと思います。http://qiita.com/kanetai

Boost.Program_options

Boost.Program_options

Boost C++をチューンアップする最先端ライブラリの序章で、紹介されていて、便利そうなので使ってみた。コマンドライン引数やコンフィギュレーションファイルから取得し、プログラムの設定オプションを簡単に設定できる。(Cなら非標準のgetopt()がある。)

名前空間/ヘッダ

長いのでエイリアスpoを使う

#include<boost/program_options>
namespace po = boost::program_options;

使い方

po::options_description opt("option");

てな感じでpo::options_descriptionを宣言する。

次に指定できるオプションをadd_options()で加える。

std::string variable
opt.add_options() //operator()で繋いでいけばおk
    ("option1,1", po::value<string>(&variable) ,"o1 description")
    ("option2,2", po::value<string>()->default_value("デフォ"), "o2 description");

ここで、第二引数にpo::value()を入れると -o1 value で値も受け取ることができる(省略可)。また、po::value(&variable) という風にすると変数variableに設定した値がはいる。さらに、po::value->default_value()でデフォルト値を設定することもできる。

そして、po::variables_mapを用意して、po::storeで解析したオプションを入れる。

po::variables_map argmap;
//std::ifstream ifs("conffile");
po::store( po::parse_command_line(argc,argv,opt), argmap );
//po::store( po::parse_config_file(ifs,opt), argmap);
po::notify(argmap);

コマンドラインをパースするときはpo::parse_command_line(), 設定ファイルをパースするときはpo::parse_config_file()を使えばいい。

設定した値をとるときは

string str = argmap["option1"].as<string>();

と言う風にする。

注意

コンパイル

ライブラリをリンクしないといけない。
g++でコンパイルするならこんな感じ(試してないけど)

g++ -o test -O2 test.cpp -lboost_program_options
g++ -o test -O2 test.cpp -lboost_program_options-mt
add_options()
opt.add_options()
    ("opt,o",  ,"option1")                   // おk
    ("opt2,o2", , "option2")                 // error ","の次は1文字のやつしか指定できないっぽい
    ("opt3", , "option3");                   // おk
日本語文字列

日本語でオプションの説明などを書くと、

std::cout << opt << std::endl;

で、表示したときインデントがずれちゃうかも知れない。

オプションチェック

オプションが指定されたかどうかは、

argvmap.count("option1")

でチェックする。

設定ファイルとコマンドラインの両方でオプションを受け取る場合

po::options_descriptionはaddメソッドでマージできるので、コマンドライン用と、設定ファイル用で分けといてもいい。
先にパースしたほうが優先される?(要確認)

po::notify()

ストアした後に

po::notify(argmap)

を呼ばないと、po::value(&variable)で設定したvariableに値が入らない?

例外

パースに失敗したり、argmapでないオプションを指定したりするとstd::exceptionが投げられるので、例外処理するならこんな感じ。

try{
        po::store( po::parse_command_line(argc,argv,opt), argmap );
                                 ・
                                 ・
                ・
}catch(std::exception& ex){
	cerr << "option parsing error: " << ex.what() << endl << opt << endl;
	exit(1);
}
Program_options_TEST.cpp
#include<iostream>
#include<string>
#include<sstream>
#include<fstream>
#include<vector>
#include<boost/program_options.hpp>
using namespace std;
namespace po = boost::program_options;

class myoption{
private:
	po::variables_map argmap; //オプションの値が読み込まれるところ
public:
	myoption(int argc, char* argv[]){
		argmap.clear();
		ostringstream oss;
		oss << endl
			<< "Program description:" << endl
			<< "Test program(Boost.Program_options)";
		//オプションの概要
		po::options_description desc(oss.str()); //system description
		po::options_description command_line("Command line");
		po::options_description config_file("Configuration file");
		
		string filename = "no configuration file";
		//コマンドラインで指定できるオプション
		command_line.add_options()
			("help,h"	,						"print help message")		//値を受け取らないオプション
			("string,s"	, po::value<string>()->default_value("default"),"input string")			//デフォルト値の設定が可能
			("int,i"	, po::value<int>(),				"input int")
			("conf,c"	, po::value<string>(&filename),			"configuration file name");	//optionを格納する変数の指定が可能
		//設定ファイルで指定できるオプション
		config_file.add_options()
			("string,s"	, po::value<string>()->default_value("default"),"input string")
			("int,i"	, po::value<int>(),				"input int")
			("debug,d"	,						"debug option")
			("vector,v"	, po::value< vector<int> >(),			"input vector");	//std::vector<*>で複数の値の受け取りが可能
		//マージ
		desc.add(command_line).add(config_file);

		//オプションの解析
		try{
			po::store( po::parse_command_line(argc,argv,command_line), argmap);
			if( argmap.count("conf") ){ // -c があればconfファイルをパースする
				ifstream confstream( argmap["conf"].as<string>().c_str() );
				po::store( po::parse_config_file(confstream,config_file), argmap);
			}
			//ヘルプ表示がある, あるいは, 必須引数が抜けていた場合
			if( argmap.count("help") || !argmap.count("int") ){
				cerr << desc << endl;
				argmap.count("help") ? exit(0) : exit(1);
			}
			po::notify(argmap); //これを呼ぶと上で設定した filenameに値が入る
		}catch(std::exception& ex){
			cerr << "option parsing error: " << ex.what() << endl
				 << desc << endl;
			exit(1);
		}	
	}

	string get_string(){ return argmap["string"].as<string>(); }
	int get_int(){ return (argmap.count("int") ? argmap["int"].as<int>() : -1); }
	string get_conffilename(){ return (argmap.count("conf") ? argmap["conf"].as<string>() : "no configuration file" ); }
	vector<int> get_vector(){ return (argmap.count("vector") ? argmap["vector"].as< vector<int> >() : vector<int>() ); }
	bool get_debug_option(){ return (bool)argmap.count("debug"); }

	void dump(){
		ostringstream oss;
		oss 	<< "string: "	<< get_string()		<< endl
			<< "int: "	<< get_int()		<< endl
			<< "conf file: "<< get_conffilename()	<< endl
			<< "vector: ( ";
		vector<int> v=get_vector();
		for( int i=0; i < v.size(); i++) oss << v[i] <<" ";
		oss << ")" << endl
			<< "debug option: " << get_debug_option() << endl;
		cout << oss.str() << endl;
	}

};

int main(int argc, char* argv[]){
	myoption opt(argc, argv);
	opt.dump();

	return 0;
}
test.conf
# Configuration file
string = CONFIG
int = -1
# debug = true
vector = 1 # vector< int >を取るようにしているので何個でもおk
vector = 2
vector = 3
#int = -2 #複数書くと例外が発生 multiple occurrences とでる