最近、C++のコードをちまちまいじっていたところ、設定ファイルとかの外部から渡すファイルって階層構造にしたほうが作りやすいし、読みやすいんだよな、これをそのままC++で読ませたいな...と思い立って調べました。
結論から言うと無事読めるようになったのですが、結構苦労したので備忘録を残しておきます。
今回はこちらのjbeder/yaml-cppを使わせていただきました。提供方法はバイナリファイルではなくソースコードなので自分の環境でビルドしてバイナリ化してから使う必要があります。本当は最新版を使いたかったのですが、ビルドがうまくいかなかったのでver0.3.0を使いました。ver0.3とver0.6では使い方が結構違うようなのでご注意を。
基本的なインストール方法は公式の通りです。日本語の解説記事はこちらが参考になります。
ついでに私が行った手順を大雑把に手順を書いておきます。
-
GitHubのリポジトリからファイルをダウンロードする。今回は
ver-0.3.0
を使用する -
CMakeをダウンロードし、インストールする。今回は
ver-3.0.0-rc2
を使用する。 - ダウンロードしてきた
cpp-yaml
の圧縮ファイルを解凍する - 解凍したフォルダ内に
build
という名前の空フォルダを作成する(この中にcamkeしたバイナリを保存します) - cmakeでsourceに
CMakeList.txt
が入ったフォルダを指定してcmakeする。今回はGUIでcmakeした。このとき、どのコンパイラでcmakeするかによって32bitか64bitかが決まるので注意- GUIを使った場合
- srcにcpp-yamlのルートフォルダを出力先にbuildフォルダを指定して
Configure
ボタンをクリックする - コンパイラとして何を使うか聞かれるので選択する。今回はVisualStudio2012がインストールされている環境で64bit版で使用したいので
Visual Studio 11 2012 Win64
を選択 - 無事完了し、
Configuring done
が表示されればOKなので、次にGenerate
ボタンをクリックする - 無事
Generating done
が表示されればOK
- srcにcpp-yamlのルートフォルダを出力先にbuildフォルダを指定して
- GUIを使った場合
-
build
フォルダ内にソリューションファイル.sln
が生成されてるはずなのでVSでこれを開く - プラットフォームがx64になっていることを確認し、ビルドモードを
RelWithDebInfo
に設定してビルドする。このときビルド対象にInstall
を含めると適当な位置にライブラリを配備してくれるらしい。試していないので不明。 - 無事ビルドが成功し、Installを指定しなかった場合は、RelWithDebInfoフォルダ内の
.lib
ファイルを取り出してC++でスタティックライブラリを読めるように設定する。わからなければ以下を参考にする - include内のyaml-cppフォルダをごそっと組み込みたいソフトで読める位置に設定する。より具体的にはVSのヘッダーファイルの設定をする
- あとは公式にそって書いていけばよい。
まず、このような形式のymlファイルを用意します。内容は適当で意味はありませんです。ファイル名も適当にinfo.yml
とでもしておきます。
- enemy:
- name: enemy0
hp: 100
atk: 10
- name: enemy1
hp: 50
atk: 20
- name: enemy2
hp: 200
atk: 30
world: japan
player: sashimimochi
次にymlの形式に合わせてC++を実装します。 基本的には参考にさせていただいたページの通りですが、複数の要素を持つ場合にループで回す部分を改良しています。 インクルードはするとして、
#include "yaml-cpp\yaml.h"
まず、受け取る情報に合わせて構造体を実装します。ymlのデータが階層構造になっていれば階層分作成します。
struct Enemy{
string name;
int hp;
int atk;
}
struct Info{
vector<Enemy> enemy;
string world;
string player;
}
これを処理するオペレーターを用意します。オペレーターも作成した構造体分作成します。
void operator >> (const YAML::Node& node, Enemy& enemy){
node["name"] >> enemy.name;
node["hp"] >> enemy.hp;
node["atk"] >> enemy.atk;
}
void operator >> (const YAML::Node& node, Info& info){
const YAML::Node& enemies = node["enemy"];
for(int i=0;i<enemies.size();i++){
Enemy enemy;
enemies[i] >> enemy;
info.enemy.push_back(enemy);
}
node["world"] >> info.world;
node["player"] >> info.player;
}
そしてこれを読み込む関数を実装します。
void loadYMLFile(string ymlpath){
string name;
int hp;
int atk;
string world;
string player;
try{
ifstream fin(ymlpath);
YAML::Parser parser(fin);
YAML::Node doc;
parser.GetNextDocument(doc);
Info info;
for(int i=0;i<doc.size();i++){
Info info; doc[i] >> info;
for(int j=0;j<info.enemy.size();j++){
name = info.enemy[j].name;
hp = info.enemy[j].hp;
atk = info.enemy[j].atk;
cout << "name:" << name << "\n" << "HP:" << hp << "\n" << "ATK:" << atk << endl;
}
world = info.world;
player = info.player;
cout << "world:" << world << "\n" << "player:" << player << endl;
}
}catch(YAML::ParserException& e){
cerr << e.what() << endl;
}
}
無事ymlファイルに入力した情報がコンソールに出力されれば成功です。 まとめて書くとこうなる。 cpp-yaml-reader.cpp
#include "yaml-cpp\yaml.h"
#include <string>
#include <fstream>
struct Enemy{
string name;
int hp;
int atk;
}
struct Info{
vector<Enemy> enemy;
string world;
string player;
}
void operator >> (const YAML::Node& node, Enemy& enemy){
node["name"] >> enemy.name;
node["hp"] >> enemy.hp;
node["atk"] >> enemy.atk;
}
void operator >> (const YAML::Node& node, Info& info){
const YAML::Node& enemies = node["enemy"];
for(int i=0;i<enemies.size();i++){
Enemy enemy;
enemies[i] >> enemy;
info.enemy.push_back(enemy);
}
node["world"] >> info.world;
node["player"] >> info.player;
}
void loadYMLFile(string ymlpath){
string name;
int hp;
int atk;
string world;
string player;
try{
ifstream fin(ymlpath);
YAML::Parser parser(fin);
YAML::Node doc;
parser.GetNextDocument(doc);
Info info;
for(int i=0;i<doc.size();i++){
Info info; doc[i] >> info;
for(int j=0;j<info.enemy.size();j++){
name = info.enemy[j].name;
hp = info.enemy[j].hp;
atk = info.enemy[j].atk;
cout << "name:" << name << "\n" << "HP:" << hp << "\n" << "ATK:" << atk << endl;
}
world = info.world;
player = info.player;
cout << "world:" << world << "\n" << "player:" << player << endl;
}
}catch(YAML::ParserException& e){
cerr << e.what() << endl;
}
}
int main(int argc, char* argv[]){
string ymlpath = argv[1];
loadYMLFile(ymlpath);
return 0;
}
これをコンパイルして引数渡しで先ほどのymlファイルのパスを渡してあげると表示してくれる形にしてみました。
$> cpp-yaml-reader.exe info.yml
これでめでたくC++でymlファイルが扱えるようになりました。設定情報をcsvファイルとかを使って頑張って階層構造を組まなくていいようになりました。マジックナンバーではなくキーで指定できるのも地味にうれしいです。