Help us understand the problem. What is going on with this article?

C++のコンテナにコンテナつっこんだお話

More than 1 year has passed since last update.

初投稿になります。せしまるです。

タイトル通りの内容を書いていきますが、なぜそんなことをしたのかザックリと説明します。
タイトル読みにくい

なんでそんなことしたの?

ファイルから値を読み込んでstd::map(std::vectorでもよかったけど)に格納。
使うときにそのコンテナを呼び出すということをやることになりました。

ご存知の通りstd::mapはキーと値をペアで格納して、その後値が欲しいときはキーで呼び出します。
キーって呼び方は何度聞いてもかっこいい

// 宣言(今回は名前と年齢をペアにしてみる)
// std::map<キーの型,値の型> map;
std::map<std::string,int> map;

// 要素の追加
map["seshimaru"] = 22;

// 使うとき
int age = map.at("seshimaru");
printf("せしまるの年齢は%d歳",age); // せしまるの年齢は22歳

こんな感じで、ファイルにあるキーと値を読み込んでstd::mapに追加していけばできるじゃないか。
この時想定していたファイルはこんな感じ。
ちなみにxmlです。

<?xml version="1.0" encoding="utf-8"?>
<!-- 本当はもっとある。 -->
<setting>
    <data key="a" value=1 />
    <data key="b" value=2 />
    <data key="c" value=3 />
</setting>

簡単だーほかのところやっとこ、なんて考えたのが失敗でした。
いざ来たファイルがこんな感じ。

setting.xml
<?xml version="1.0" encoding="utf-8"?>
<setting>
  <!-- <data 構造体名 メンバ変数名 下限値 上限値 />. -->
    <data structname="a" paramname="aa" min="0" max="5" /> 
    <data structname="a" paramname="bb" min="1" max="10" /> 
    <data structname="a" paramname="cc" min="-2" max="2" /> 
    <data structname="b" paramname="aa" min="1" max="100" /> 
    <data structname="b" paramname="bb" min="1" max="5" /> 
    <data structname="c" paramname="aa" min="1.5" max="2.5" /> 
    <data structname="c" paramname="bb" min="0" max="5" /> 
    <data structname="c" paramname="cc" min="-270" max="270" /> 
    <data structname="c" paramname="dd" min="0" max="15.5" /> 
</setting>

どういうことかというと、まず下のような構造体がいくつも存在します。
その中の1メンバの設定できる上限値・下限値が一行に書いてある、という感じです。
(イメージとして画面で値入力して保存ボタン押すと設定値が入る感じ)

struct.cpp
// setting.xmlのdata1個目.
typedef struct _STRUCT1_a{
    int aa;
    int bb;
    int cc;
} STRUCT1_a;

実装

最初見たときはへーふーんでした(へー、ふーんw)。
でもいざ実装しようと思った時に悩みに悩みました。
構造体名重複してんじゃん。どうすんだ!

std::mapにstd::mapでも突っ込んでみるか・・・!?
そして生み出されたのがこれ

container.cpp
// std::map<構造体名, std::map<メンバ変数名,std::vector<std::string>>> map.
// std::vectorには上限値下限値を順番に入れる.
std::map<std::string,std::map<std::string,std::vector<std::string>>> map;

うわぁ見づらいしきたない・・・なんて思ってしまったがこれならいける気がする。

さっきの設定ファイルを読み込んで上記のコンテナに格納してみる。

reader.hpp
// 必要なインクルード.今回はboostを使用する.
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

// XMLの定義.
#define XML_NODE "setting"
#define STRUCT_NAME "<xmlattr>.structname"
#define PARAM_NAME "<xmlattr>.paramname"
#define VALUE_MIN "<xmlattr>.min"
#define VALUE_MAX "<xmlattr>.max"


static bool ReadXML(char* path,
      std::map<std::string, std::map<std::string, std::vector<std::string>>>& map){

    bool flag = false;
    boost::ptree pt;
    std::string before = "";
    // メンバ変数map.
    std::map<std::string, std::vector<std::string>> innermap;

    // ptreeの読み込み部分は長いので省略.

    // 読み込んだものをmapに入れていく.
    for(auto &treevalue : pt.get_child(XML_NODE){
        boost::optional<std::string> structname =
                    treevalue.second.get_optional<std::string>(STRUCT_NAME);
        boost::optional<std::string> paramname =
                    treevalue.second.get_optional<std::string>(PARAM_NAME);
        boost::optional<std::string> min =
                    treevalue.second.get_optional<std::string>(VALUE_MIN);
        boost::optional<std::string> max =
                    treevalue.second.get_optional<std::string>(VALUE_MAX);

        if(structname){
            // 最初の一回だけ.
            if( before == "" ){
                before = structname.get();
            }
            // 別の構造体の行になったらmapに格納してメンバ変数mapを初期化.
            if( before != structname.get()){
                map[before] = innermap;
                innermap.clear();
                before = structname.get();
            }

            std::vector<std::string> array;
            array.push_back(min.get());
            array.push_back(max.get());
            innermap[paramname] = array;
            flag = true;
        }
    }
    // 最後に読み込んだものを追加.
    if( flag ){
        map[before] = innermap;
    }
    return flag;
}

これでコンテナに格納されたはず。

実際に呼び出してみる。

call.cpp
#include "reader.hpp"

int main(int argc, char* argv[]){
    std::map<std::string, std::map<std::string, std::vector<std::string>>> map;
    ReadXML("~/home/user/setting.xml", map);

    // structname="a"の中身.
    auto innermap = map.at("a");

    // paramname="aa"の中身.
    auto array = innermap.at("aa");

    // 出力.
    printf("min = %s, max = %s\n",array[0].c_str(), array[1].c_str());
}

出力結果

min = 0, max = 5

別のstructname、paramnameを突っ込んでも問題なく取れます.

感想

これ作ってるときは「もっと簡単にできないかなー」とか考えてたんですが、C++歴半年の私には思いつかなかった(';')

ここはこうしたほうがいい!というコメントなどは大歓迎です!

ありがとうございました。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away