この記事はKMC Advent Calendar 2015 - Adventar 21日目の記事です。 20日目の記事は、zetaさんの天下一品フェア2015でした。
はじめに
KMCのastatineです。
私は、昨年度の間ずっと衛星のデータ解析のコードをC++で開発し続けた結果、 「C++って人間*1に分かりやすい言語ですね。」と言う闇魔道士になってしまいました。
このKMC Advent Calenderは、 Pietという謎言語の話だったり、 #shellというslackの闇channelの話だったり、 旅行記だったりと、 KMCらしい混沌とした流れになっています。 そんな中、私は私らしくC++の話をしようと思います。
皆さんは、C++で計算結果等をファイルに書き出すときどのようにしているでしょうか?
カンマやスペース区切りでの出力は簡単なコードで行えます。 行列や関数のグラフではそれで十分ですが、複雑なことをしようとするとそれでは難しくなってきます。 私は、次のような問題に直面しました。
「xの平均は0番目、xの分散が1番目だからyの平均は2番目……」
「さっきxの最大・最小を追加したから、yの平均は4番目に直さないと」
「最初にLabelを追加したくなった。」
そうすると、 出力に何か追加しようとするたびにインデックスを指で数えて修正し、 正しく出力されていることを確認しようとインデックスを指で数えて修正し……。 このようなことを数回繰り返すと、この先に待っているのは闇であることに気付くでしょう。
だからとって、オレオレ構文を採用すると、そこには互換性が一切無いという闇が広がっています。 Python・Ruby等に比べて文字列処理に拙いC++で、そのオレオレ構文のParserを書くのもまた闇です。
こんな中颯爽と現れるのが、boost::property_tree
という光です。
boost::property_tree
boost::property_tree
は、偉大なるboostの一部であり、任意の形状の木構造を扱うライブラリです。
木構造のデータはboost::property_tree::ptree
(以下ptree
と表記する)として格納され、各要素にはタグを通してアクセスすることができます。
そして、このライブラリにはXML,JSON,INIとptree
間の変換関数が用意されています。
これで、インデックスを指で数えたりオレオレ構文を駆使したりする闇から逃れ、 XML・JSON*2という光に包まれることができます。
このboost::property_tree
の使い方は、
boostのドキュメント
を読めばわかります。
必要に応じて、
C++のドキュメント
や
stack overflow
を読むといいでしょう。
私が作成している
サンプルコード*3
もありますので、是非参考にして下さい。
*4
ptree
は、tag(std::string
)とnode(ptree
)のstd::pair
のコンテナのように扱うことができます。
begin,end,push_back,insert,erase,...のようなSTLコンテナと共通の関数を持ち、
range-based-forで回すことが出来ます。
以下、XMLとptree
間の変換・ptree
と値の変換に分けて話しましょう。
XMLとptree間の変換
boost/property_tree/xml_parser.hpp
をincludeすると次の関数が使えるようになります。
boost::property_tree::read_xml
stream(第1引数)のXMLをParseし、ptree(第2引数)に書き込む。
第3引数(省略可能)には、改行や空白の処理方法を指定することが出来る。boost::property_tree::write_xml
ptree(第2引数)から生成されるXMLを、stream(第1引数)に書き込む。
第3引数(省略可能)には、インデントや改行の形式を指定することができる。
ptreeと値の変換
ptreeからnodeを取得する
ptreeからtag以下のnode(XMLでは<tag>....</tag>
)を取得する方法の一例
ptree.get_child("tag"); ptree.get_child_optional("tag");
optionalと末尾につけると、返り値はboost::optional
に包まれた値となります。
これもまた、見慣れないboostのライブラリかもしれませんが、
optional
は非常に便利な型であり、
C++17でstdに入る予定なので身につけて損は無いです
*5。
ptreeから値を取得する
ptreeからtag内のint
の値(XMLでは<tag>value<\tag>
のvalue)を得る方法の一例
ptree.get<int>("tag"); ptree,get_optional<int>("tag"); ptree.get("tag", 0); ptree.get_child("tag").get_value<int>();
tagの他にも、default値やTranslator(後述)を引数に設定する事ができます。
ptreeにnodeを設定する
ptreeのtag以下にnodeを設定する方法の一例
ptree.add_child("tag", node); ptree.put_child("tag", node); ptree.insert(ptree.end(), node.begin(), node.end());
addとputの違いはtagというタグが既に存在するか否かで動作が変わります。 putの場合、tagというタグが既に存在した場合それを上書きします。 一方addの場合は、上書きせずに追加する形になります。
ptreeに値を設定する
ptreeのtagに値0を設定する(XMLでは
ptree.add_value("tag", 0); ptree.put_value("tag", 0); ptree.push_back(std::make_pair("tag", 0)); ptree.insert(ptree.end(), std::make_pair("tag", 0));
addとputの違いはnodeと同じです。
add_value,put_valueでは、空文字(""
)をタグにすることは出来ませんが、
std::pairを介すると空文字をタグに出来てしまいます。
ここらへんの挙動は、実際にコードを書いて確かめるのが良いでしょう。
Translator
int,double,bool,std::stringのようなプリミティブ型は、 template parameterやdefault値を通して型の情報を与えてやれば簡単に取得・設定することが出来ます。 一方、プリミティブ型で無い型はそのままでは取得・設定に一手間必要になります。
その手間を省く方法として、Translatorの設定があります。
MyEnum
へのTranslatorは次ようなclassになります。
enum class MyEnum; struct MyEnumTranslator{ using internal_type = boost::property_tree::ptree::data_type; using external_type = MyEnum; boost::optional<external_type> get_value( const internal_type&) const; boost::optional<internal_type> put_value( const external_type&) const; };
とりあえず、get_valueとput_valueがあればよいと思われます。
このMyEnumTranslatorをget等の引数に加えれば、
MyEnum
をプリミティブ型と同様に取得・設定することができるようになります。
また、引数として与えるのも面倒ならば、 このTranslatorをboost::property_tree内に追加することが出来ます。 その方法は、次のコードを書き加えるだけです。
namespace boost{ namespace property_tree{ // card Type Translator template<typename Ch, typename Traits, typename Alloc> struct translator_between< std::basic_string<Ch, Traits, Alloc>, MyEnum>{ using type = MyEnumTranslator; }; }// end namespace property_tree }// end namespace boost
このようにして、boost::property_tree::translator_between
にMyEnum
の変換方法を登録してやると、
getの引数にTranslatorを指定する必要がなくなり、
プリミティブ型と完全に同じように扱うことができるようになります。
このようなTranslatorを作成することで、 boost::property_treeは格段に使いやすくなります。 私の作成したTranslatorが サンプルコード にありますので、参考にしていただけたら幸いです。
最後に
注意しなければならない点を上げるとすれば、
あくまでこのライブラリはptree
を扱うためのライブラリであり、
XML・JSONを扱うためのライブラリでは無いことです。
そのため、XML・JSONに完全に対応しているわけではありません
*6。
しかしそれでも、boost::property_tree
は
カンマ・スペース区切りよりもわかりやすく、オレオレ構文よりも手軽に
情報を出力する手段となることでしょう。
明日のKMC Advent Calenderは、 lp6mのバーサライタです。