読者です 読者をやめる 読者になる 読者になる

KMC活動ブログ

京大マイコンクラブの活動の様子を紹介します!!

boost::property_tree

KMCアドベントカレンダー2015

この記事は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を追加したくなった。」

そうすると、 出力に何か追加しようとするたびにインデックスを指で数えて修正し、 正しく出力されていることを確認しようとインデックスを指で数えて修正し……。 このようなことを数回繰り返すと、この先に待っているのは闇であることに気付くでしょう。

だからとって、オレオレ構文を採用すると、そこには互換性が一切無いという闇が広がっています。 PythonRuby等に比べて文字列処理に拙いC++で、そのオレオレ構文のParserを書くのもまた闇です。

こんな中颯爽と現れるのが、boost::property_treeという光です。

boost::property_tree

boost::property_treeは、偉大なるboostの一部であり、任意の形状の木構造を扱うライブラリです。 木構造のデータはboost::property_tree::ptree(以下ptreeと表記する)として格納され、各要素にはタグを通してアクセスすることができます。 そして、このライブラリにはXML,JSON,INIとptree間の変換関数が用意されています。

これで、インデックスを指で数えたりオレオレ構文を駆使したりする闇から逃れ、 XMLJSON*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で回すことが出来ます。

以下、XMLptree間の変換・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では0)方法の一例

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_betweenMyEnumの変換方法を登録してやると、 getの引数にTranslatorを指定する必要がなくなり、 プリミティブ型と完全に同じように扱うことができるようになります。

このようなTranslatorを作成することで、 boost::property_treeは格段に使いやすくなります。 私の作成したTranslatorが サンプルコード にありますので、参考にしていただけたら幸いです。

最後に

注意しなければならない点を上げるとすれば、 あくまでこのライブラリはptreeを扱うためのライブラリであり、 XMLJSONを扱うためのライブラリでは無いことです。 そのため、XMLJSONに完全に対応しているわけではありません *6。 しかしそれでも、boost::property_treeは カンマ・スペース区切りよりもわかりやすく、オレオレ構文よりも手軽に 情報を出力する手段となることでしょう。

C++に光あれ*7

明日のKMC Advent Calenderは、 lp6mのバーサライタです。

*1:C/C++の仕様を理解し、Object指向を理解し、STLを使いこなし、メモリの感覚を身につけ、数多のバグを踏み超えて来た人間

*2:INIはネストに対応していないので闇

*3:鋭意製作中

*4:…………流石に、これで終わってはだめでしょうね。

*5:私がboost::property_treeを使ってよかったと思うことの半分が、 この型を使えるようになったと言っても過言ではない。

*6:例えば、出力するXMLにコメントを付けることは出来ない。

*7:C++の闇に呑まれよ