5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

std::mapのシリアライズ

Posted at

はじめに

わけあってstd::mapをシリアライズ/デシリアライズしたい。boost::serializationを使えば一発らしいんだけど、わけあってあまりboostを使いたくない。

というわけで車輪の再開発をする。書いたコードは

においておく。

方針

std::mapを継承したserializable_mapクラスを作る。こういう感じ。

template<class Key, class Value>
class serializable_map : public std::map<Key, Value> {
// ここを実装したい
};

シリアライズとデシリアライズメンバ関数は、こんな形になっていてほしい。

  std::vector<char> serialize() {
    std::vector<char> buffer;
    std::stringstream ss;
    for (auto &i : (*this)) {
      Key str = i.first;
      Value value = i.second;
      write(ss, str);
      write(ss, value);
    }
    size_t size = ss.str().size();
    buffer.resize(size);
    ss.read(buffer.data(), size);
    return buffer;
  }

  void deserialize(std::vector<char> &buffer) {
    offset = 0;
    while (offset < buffer.size()) {
      Key key;
      Value value;
      read(buffer, key);
      read(buffer, value);
      (*this)[key] = value;
    }
  }

つまり、シリアライズは全てのキーと値をstd::stringstreamに突っ込んで、最後にstd::vector<char>に変換してそれを返す。デシリアライズはstd::vector<char>から直接読み込む。書き込みはstd::stringstreamを使ってるので書き込み位置の制御は不要だが、読み込みはstd::vector<char>を使ってるので、現在の読み込み位置を覚えておく必要がある。それをoffsetで覚えている1

基本的にテンプレートを使うが、std::stringだけ別扱いにする。あとはこのwritereadを実装すれば良い。

実装

読み込み、書き込みともに、intとかそういうPODなら

  template<class T>
  void write(std::stringstream &ss, T &t) {
    ss.write((char*)(&t), sizeof(t));
  }

  template<class T>
  void read(std::vector<char> &buffer, T &t) {
    t = (T)(*(buffer.data() + offset));
    offset += sizeof(T);
  }

 でいける。

ただし、std::stringの場合は長さが不定なので、最初に長さを、次に実体を書き込む必要がある。というわけで、そういうふうにテンプレートを特殊化する。

  void write(std::stringstream &ss, std::string &str) {
    size_t size = str.size();
    ss.write((char*)(&size), sizeof(size));
    ss.write((char*)(str.data()), str.length());
  }

  void read(std::vector<char> &buffer, std::string &str) {
    size_t size = (int)(*(buffer.data() + offset));
    offset += sizeof(size_t);
    std::string str2(buffer.data() + offset, buffer.data() + offset + size);
    str = str2;
    offset += size;
  }

後はテストのために、中身を表示する関数showも作っておこう。

  void show(void) {
    for (auto &i : (*this)) {
      std::cout << i.first << ":" << i.second << std::endl;
    }
    std::cout << std::endl;
  }

これで完成。

テスト

こんなテストを書いた。

void
test1(void) {
  std::cout << "string->int" << std::endl;
  serializable_map<std::string, int> m;
  m["test1"] = 1;
  m["test2"] = 2;
  m["test3"] = 3;
  std::vector<char> buffer = m.serialize();
  serializable_map<std::string, int> m2;
  m2.deserialize(buffer);
  m2.show();
}

void
test2(void) {
  std::cout << "int->string" << std::endl;
  serializable_map<int, std::string> m;
  m[1] = "test1";
  m[2] = "test2";
  m[3] = "test3";
  std::vector<char> buffer = m.serialize();
  serializable_map<int, std::string> m2;
  m2.deserialize(buffer);
  m2.show();
}

void
test3(void) {
  std::cout << "int->int" << std::endl;
  serializable_map<int, int> m;
  m[1] = 1;
  m[2] = 2;
  m[3] = 3;
  std::vector<char> buffer = m.serialize();
  serializable_map<int, int> m2;
  m2.deserialize(buffer);
  m2.show();
}

int
main(void) {
  test1();
  test2();
  test3();
}

つまり、<std::string, int><int, std::string>, <int, int>のそれぞれについてシリアライズ、デシリアライズを試している。実行結果はこうなる。

$ g++ -std=c++11 test.cpp -o a.out
$ ./a.out

string->int
test1:1
test2:2
test3:3

int->string
1:test1
2:test2
3:test3

int->int
1:1
2:2
3:3

うまくいってるみたいですね。

まとめ

std::mapをboostを使わずにシリアライズしたかったので車輪の再開発をした。読み込みのところで、最初は返り値の型によるオーバーロードをやろうとしたんだけど、ナイーブにやるとクラス内の関数テンプレートの特殊化が必要になって面倒くさい(たぶん)。いろいろ考えたんだけど、結局引数で特殊化するのが一番簡単な気がしてそうしてしまった。

どうでもいいけど、テンプレートの実装をミスるとやたら大量のエラーメッセージ出るし意味不明だしで困ったもんですね。

  1. なんで読み込みをstd::istream系で実装しなかったのか覚えてない。なんか理由があった気がする。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?