2017年6月7日 Theolizer®最新版v1.1.0へ対応するために修正しました。
最新版のソース一式をGistに置いてます。

こんにちは。田原です。

プログラムの開発中に構造体やクラス、enum型の定義変更は日常茶飯事ですね。そして、古いプログラムで保存したファイルを新しいプログラムで読む必要があります。Theolizer®ではこれらに2種類の方法を組み合わせて対処します。今回は定義の変更方法について解説し、次回はバージョン・アップ/ダウン処理について解説します。

1.どんな変更が可能なのか

1-1.クラスと構造体の場合

クラスや構造体はメンバ変数を1つ1つ自動的に保存しますが、その時メンバ変数とその値の対応方法として2種類用意しています。

  1. メンバ変数名で対応
    変数名で対応しているので定義順序を変更できます。また、任意の場所に追加できますし、任意の変数を削除できます。
    クラスや構造体が若い内に使うモードです。デフォルトではこのモードとなります。今回はこちらについて説明します。

  2. クラス/構造体内の定義順で対応
    順序だけで対応していますので、順序が狂うような変更はできません。メンバ変数リストの最後のみ追加/削除が可能です。
    しかし、ある程度枯れてきたら、変数名をシリアライズ・データへ記録することが勿体無く感じます。そのような時に使う機能です。

1-2.enum型の場合

enum型はその設定値をシンボル名、もしくは、シンボル値のどちらかで保存できます。

  1. シンボル名で保存
    enum型の変数に格納されている値を文字列としてシンボル名にて保存します。つまり、値を変更してもシンボル名を変更していなければ変更前に保存されたデータを適切に回復できます。
    enum型が若い内に使うモードです。デフォルトではこのモードとなります。今回はこちらについて説明します。

  2. シンボル値で保存
    enum型の変数に格納されている値を数値型でそのまま保存します。
    値だけを保存しますので、シリアライズ・データへの負担が小さいです。枯れたenum型で使用する機能です。

2.サンプル・プログラム

今回はシリアライズ・データの内容を良く見た方がよいので、家計簿アプリは使いません。
小さな小さなenum型と構造体を保存/回復するコードを書きました。

2-1.変更前のプログラム

2-1-1.構造体定義
main_a.h
[cpp] //############################################################################
// Theolizer解説用サンプル・プログラムExtra-1
//############################################################################

#if !defined(MAIN_H)
#define MAIN_H

// ***************************************************************************
// インクルード
// ***************************************************************************

// 標準ライブラリ
#include <string>

// Theolizerライブラリ
#include <theolizer/serializer_json.h>

// ***************************************************************************
// サンプルenum型定義
// ***************************************************************************

enum Foo
{
eFoo0,
eFoo1
};

// ***************************************************************************
// サンプル構造体定義
// ***************************************************************************

struct Bar
{
int mBar0;
short mBar1;
std::string mBar2;

Bar()
{
mBar0=0;
mBar1=0;
mBar2="";
}
};
#endif
[/cpp]

2-1-2.データの保存と回復
main_a.cpp
[cpp] //############################################################################
// Theolizer解説用サンプル・プログラムExtra-1
//############################################################################

#define THEOLIZER_GLOBAL_VERSION_TABLE

// ***************************************************************************
// インクルード
// ***************************************************************************

// 標準ライブラリ
#include <fstream>

// 共通定義
#include "main_a.h"

// Theolizer自動生成先
#include "main_a.cpp.theolizer.hpp"

// ***************************************************************************
// メイン
// ***************************************************************************

int main(int argc, char* argv[])
{
//—————————————————————————-
// バラメータ解析
//—————————————————————————-

bool aLoadOnly=false;
if (1 < argc)
{
std::string argv1=argv[1];
if ((argv1 == "l") || (argv1 == "L"))
{
aLoadOnly=true;
}
}
std::cout << theolizer::print("LoadOnly:%d\n", aLoadOnly);

//—————————————————————————-
// 保存
//—————————————————————————-

if (!aLoadOnly)
{
// データを生成する
Foo aFooA=eFoo0;
Foo aFooB=eFoo1;
Bar aBar;
aBar.mBar0=10;
aBar.mBar1=11;
aBar.mBar2="12";

// 保存先のファイルをオープンする
std::ofstream aStream("test.json");

// シリアライザを用意する
theolizer::JsonOSerializer<> js(aStream);

// test.jsonファイルへ保存する
THEOLIZER_PROCESS(js, aFooA);
THEOLIZER_PROCESS(js, aFooB);
THEOLIZER_PROCESS(js, aBar);
}

//—————————————————————————-
// 回復
//—————————————————————————-

// 回復処理
{
// データ領域を獲得する
Foo aFooA;
Foo aFooB;
Bar aBar;

// 回復元のファイルをオープンする
std::ifstream aStream("test.json");

// シリアライザを用意する
theolizer::JsonISerializer<> js(aStream);

// test.jsonファイルから回復する
THEOLIZER_PROCESS(js, aFooA);
THEOLIZER_PROCESS(js, aFooB);
THEOLIZER_PROCESS(js, aBar);

// Json形式で表示する
theolizer::JsonOSerializer<> jout(std::cout);
THEOLIZER_PROCESS(jout, aFooA);
THEOLIZER_PROCESS(jout, aFooB);
THEOLIZER_PROCESS(jout, aBar);

// 普通に表示する
std::cout << theolizer::print(u8"(eFoo0=%d, eFoo1=%d)\n", eFoo0, eFoo1);
std::cout << theolizer::print(u8"aFooA=%d\n", aFooA);
std::cout << theolizer::print(u8"aFooB=%d\n", aFooB);
std::cout << theolizer::print(u8"aBar.mBar0=%d\n", aBar.mBar0);
std::cout << theolizer::print(u8"aBar.mBar1=%d\n", aBar.mBar1);
std::cout << theolizer::print(u8"aBar.mBar2=%s\n", aBar.mBar2);
}

return 0;
}
[/cpp]

2-1-3.自動生成されたソース
main_a.cpp.theolizer.hpp
[cpp] #ifdef THEOLIZER_WRITE_CODE // ###### Foo ######

#define THEOLIZER_GENERATED_LAST_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kLastVersionNo,1)
#define THEOLIZER_GENERATED_FULL_AUTO Foo

// —<<< Version.1 >>>—

#define THEOLIZER_GENERATED_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kVersionNo,1)
#define THEOLIZER_GENERATED_ENUM_NAME u8"Foo"
#define THEOLIZER_GENERATED_SAVE_TYPE estName
#define THEOLIZER_GENERATED_BASE_TYPE int
#define THEOLIZER_GENERATED_ENUM_LIST()\
THEOLIZER_GENERATED_ELEMENT((u8"eFoo0"),(0),(0))\
THEOLIZER_GENERATED_ELEMENT((u8"eFoo1"),(1),(1))
#define THEOLIZER_GENERATED_DEFAULT_VALUE 0
#include <theolizer/internal/version_enum.inc>
#undef THEOLIZER_GENERATED_VERSION_NO

#endif//THEOLIZER_WRITE_CODE // ###### Foo ######

#ifdef THEOLIZER_WRITE_CODE // ###### Bar ######

#define THEOLIZER_GENERATED_LAST_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kLastVersionNo,1)
#define THEOLIZER_GENERATED_FULL_AUTO Bar

// —<<< Version.1 >>>—

#define THEOLIZER_GENERATED_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kVersionNo,1)
#define THEOLIZER_GENERATED_CLASS_NAME()\
THEOLIZER_INTERNAL_CLASS_NAME((u8"Bar"))
#define THEOLIZER_GENERATED_ELEMENT_MAP emName
#define THEOLIZER_GENERATED_ELEMENT_LIST()\
THEOLIZER_INTERNAL_ELEMENT_N((mBar0),mBar0,etmDefault,\
(theolizerD::All),\
(int))\
THEOLIZER_INTERNAL_ELEMENT_N((mBar1),mBar1,etmDefault,\
(theolizerD::All),\
(short))\
THEOLIZER_INTERNAL_ELEMENT_N((mBar2),mBar2,etmDefault,\
(theolizerD::All),\
(std::string))
#include <theolizer/internal/version_auto.inc>
#undef THEOLIZER_GENERATED_VERSION_NO

#endif//THEOLIZER_WRITE_CODE // ###### Bar ######

#ifdef THEOLIZER_WRITE_CODE // ###### Global VersionNo. Table ######
THEOLIZER_GENERATED_GLOBAL_TABLE();
#endif//THEOLIZER_WRITE_CODE // ###### Global VersionNo. Table ######
[/cpp]

enum型Fooの変数aFooA, aFooBは値ではなくシンボル名(”eFoo1″, “eFoo2″)で保存されています。

2-1-4.保存されたファイル
test.json
[cpp] {
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1] }
"eFoo0"
"eFoo1"
{
"mBar0":10,
"mBar1":11,
"mBar2":"12"
}
[/cpp]
2-1-5.実行結果
result_a.log
[test] LoadOnly:0
{
“SerialzierName”:”JsonTheolizer”,
“GlobalVersionNo”:1,
“TypeInfoList”:[1] }
“eFoo0”
“eFoo1”
{
“mBar0”:10,
“mBar1”:11,
“mBar2″:”12”
}
(eFoo0=0, eFoo1=1)
aFooA=0
aFooB=1
aBar.mBar0=10
aBar.mBar1=11
aBar.mBar2=12
[/test]

2-2.変更後のプログラムで変更前のプログラムのデータを読む

enum型Fooはシンボル値を変え、シンボルを増やしました。
構造体Barはメンバ変数の順序を変更、メンバ変数mBar0を削除、メンバ変数mBar3を追加しました。

2-2-1.構造体定義
main_b.h
[cpp] //############################################################################
// Theolizer解説用サンプル・プログラムExtra-2
//############################################################################

#if !defined(MAIN_H)
#define MAIN_H

// ***************************************************************************
// インクルード
// ***************************************************************************

// 標準ライブラリ
#include <string>

// Theolizerライブラリ
#include <theolizer/serializer_json.h>

// ***************************************************************************
// サンプルenum型定義
// ***************************************************************************

enum Foo
{
eNone,
eFoo0=1000,
eFoo1,
eFoo2
};

// ***************************************************************************
// サンプル構造体定義
// ***************************************************************************

struct Bar
{
unsigned mBar3; // 新規追加
std::string mBar2; // 順序変更
// int mBar0; // 削除
short mBar1;

Bar()
{
mBar1=0;
mBar2="";
mBar3=0;
}
};
#endif
[/cpp]

2-2-2.データの保存と回復
main_b.cpp
[cpp] //############################################################################
// Theolizer解説用サンプル・プログラムExtra-2
//############################################################################

#define THEOLIZER_GLOBAL_VERSION_TABLE

// ***************************************************************************
// インクルード
// ***************************************************************************

// 標準ライブラリ
#include <fstream>

// 共通定義
#include "main_b.h"

// Theolizer自動生成先
#include "main_b.cpp.theolizer.hpp"

// ***************************************************************************
// メイン
// ***************************************************************************

int main(int argc, char* argv[])
{
//—————————————————————————-
// バラメータ解析
//—————————————————————————-

bool aLoadOnly=false;
if (1 < argc)
{
std::string argv1=argv[1];
if ((argv1 == "l") || (argv1 == "L"))
{
aLoadOnly=true;
}
}
std::cout << theolizer::print("LoadOnly:%d\n", aLoadOnly);

//—————————————————————————-
// 保存
//—————————————————————————-

if (!aLoadOnly)
{
// データを生成する
Foo aFooA=eFoo1;
Foo aFooB=eFoo2;
Bar aBar;
// aBar.mBar0=100;
aBar.mBar1=101;
aBar.mBar2="102";
aBar.mBar3=103;

// 保存先のファイルをオープンする
std::ofstream aStream("test.json");

// シリアライザを用意する
theolizer::JsonOSerializer<> js(aStream);

// aBarをtest.jsonファイルへ保存する
THEOLIZER_PROCESS(js, aFooA);
THEOLIZER_PROCESS(js, aFooB);
THEOLIZER_PROCESS(js, aBar);
}

//—————————————————————————-
// 回復
//—————————————————————————-

// 回復処理
{
// データ領域を獲得する
Foo aFooA=eNone;
Foo aFooB=eNone;
Bar aBar;

// 回復元のファイルをオープンする
std::ifstream aStream("test.json");

// シリアライザを用意する
theolizer::JsonISerializer<> js(aStream);

// aBarをtest.jsonファイルから回復する
THEOLIZER_PROCESS(js, aFooA);
THEOLIZER_PROCESS(js, aFooB);
THEOLIZER_PROCESS(js, aBar);

// Json形式で表示する
theolizer::JsonOSerializer<> jout(std::cout);
THEOLIZER_PROCESS(jout, aFooA);
THEOLIZER_PROCESS(jout, aFooB);
THEOLIZER_PROCESS(jout, aBar);

// 普通に表示する
std::cout << theolizer::print(
u8"(eNone=%d, eFoo0=%d, eFoo1=%d, eFoo2=%d)\n",
eNone, eFoo0, eFoo1, eFoo2);
std::cout << theolizer::print(u8"aFooA=%d\n", aFooA);
std::cout << theolizer::print(u8"aFooB=%d\n", aFooB);
// std::cout << theolizer::print(u8"aBar.mBar0=%d\n", aBar.mBar0);
std::cout << theolizer::print(u8"aBar.mBar1=%d\n", aBar.mBar1);
std::cout << theolizer::print(u8"aBar.mBar2=%s\n", aBar.mBar2);
std::cout << theolizer::print(u8"aBar.mBar3=%s\n", aBar.mBar3);
}

return 0;
}
[/cpp]

2-2-3.自動生成されたソース
main_b.cpp.theolizer.hpp
[cpp] #ifdef THEOLIZER_WRITE_CODE // ###### Foo ######

#define THEOLIZER_GENERATED_LAST_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kLastVersionNo,1)
#define THEOLIZER_GENERATED_FULL_AUTO Foo

// —<<< Version.1 >>>—

#define THEOLIZER_GENERATED_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kVersionNo,1)
#define THEOLIZER_GENERATED_ENUM_NAME u8"Foo"
#define THEOLIZER_GENERATED_SAVE_TYPE estName
#define THEOLIZER_GENERATED_BASE_TYPE int
#define THEOLIZER_GENERATED_ENUM_LIST()\
THEOLIZER_GENERATED_ELEMENT((u8"eNone"),(0),(0))\
THEOLIZER_GENERATED_ELEMENT((u8"eFoo0"),(1000),(1000))\
THEOLIZER_GENERATED_ELEMENT((u8"eFoo1"),(1001),(1001))\
THEOLIZER_GENERATED_ELEMENT((u8"eFoo2"),(1002),(1002))
#define THEOLIZER_GENERATED_DEFAULT_VALUE 0
#include <theolizer/internal/version_enum.inc>
#undef THEOLIZER_GENERATED_VERSION_NO

#endif//THEOLIZER_WRITE_CODE // ###### Foo ######

#ifdef THEOLIZER_WRITE_CODE // ###### Bar ######

#define THEOLIZER_GENERATED_LAST_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kLastVersionNo,1)
#define THEOLIZER_GENERATED_FULL_AUTO Bar

// —<<< Version.1 >>>—

#define THEOLIZER_GENERATED_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kVersionNo,1)
#define THEOLIZER_GENERATED_CLASS_NAME()\
THEOLIZER_INTERNAL_CLASS_NAME((u8"Bar"))
#define THEOLIZER_GENERATED_ELEMENT_MAP emName
#define THEOLIZER_GENERATED_ELEMENT_LIST()\
THEOLIZER_INTERNAL_ELEMENT_N((mBar3),mBar3,etmDefault,\
(theolizerD::All),\
(unsigned int))\
THEOLIZER_INTERNAL_ELEMENT_N((mBar2),mBar2,etmDefault,\
(theolizerD::All),\
(std::string))\
THEOLIZER_INTERNAL_ELEMENT_N((mBar1),mBar1,etmDefault,\
(theolizerD::All),\
(short))
#include <theolizer/internal/version_auto.inc>
#undef THEOLIZER_GENERATED_VERSION_NO

#endif//THEOLIZER_WRITE_CODE // ###### Bar ######

#ifdef THEOLIZER_WRITE_CODE // ###### Global VersionNo. Table ######
THEOLIZER_GENERATED_GLOBAL_TABLE();
#endif//THEOLIZER_WRITE_CODE // ###### Global VersionNo. Table ######
[/cpp]

main_a.exeを実行し、test.jsonを生成した後、main_b.exe Lを実行しました。
main_b.exeにパラメータLを指定することで保存処理をスキップしtest.jsonを回復します。
以上の操作により、変更前のプログラムが保存したtest.jsonを変更後のプログラムが回復した結果となります。
enum型Fooがシンボル名で回復されているため、変更後のプログラムの値が回復されています。
構造体Barは変更前に存在していたメンバ変数(mBar1, mBar2)について適切に回復されています。新規追加されたmBar3は初期値のままを維持しています。そして、削除されたmBar0については破棄しますので問題を引き起こしません。

2-2-4.CMakeLists.txt

CMakeLists.txt
[text] #[[###########################################################################
Theolizer紹介用サンプルCMakeLists.txt
]]############################################################################

if("${CMAKE_VERSION}" STREQUAL "")
set(CMAKE_VERSION, 3.5.0)
endif()
cmake_minimum_required(VERSION ${CMAKE_VERSION})

message(STATUS "BOOST_ROOT=${BOOST_ROOT}")

#—————————————————————————–
# プロジェクト設定
#—————————————————————————–

set(TARGET_NAME sample)

project(${TARGET_NAME} VERSION 1.0.0)

#—————————————————————————–
# ビルド設定
#—————————————————————————–

# MSVCの通常使わないビルド・モードとZERO_CHECKプロジェクトの削除
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configs" FORCE)
set(CMAKE_SUPPRESS_REGENERATION TRUE)

# Theolizer
find_package(THEOLIZER)

# Options
if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
add_definitions(-D_UNICODE -DUNICODE)
set(CMAKE_DEBUG_POSTFIX "d")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

# MinGWの不具合(https://sourceforge.net/p/mingw-w64/discussion/723797/thread/c6b70624/#7f0a)暫定対処
if((CMAKE_COMPILER_IS_MINGW) AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj")
endif()
endif()

# —<<< ターゲットa >>>—

set(SOURCE_LIST main_a.cpp)
set(HEADER_LIST main_a.h)

add_executable(${TARGET_NAME}_a ${SOURCE_LIST} ${HEADER_LIST})
setup_theolizer(${TARGET_NAME}_a StaticWithBoost)

# —<<< ターゲットb >>>—

set(SOURCE_LIST main_b.cpp)
set(HEADER_LIST main_b.h)

add_executable(${TARGET_NAME}_b ${SOURCE_LIST} ${HEADER_LIST})
setup_theolizer(${TARGET_NAME}_b StaticWithBoost)

#—————————————————————————–
# テスト実行
#—————————————————————————–

enable_testing()
add_test(NAME ${TARGET_NAME}_a COMMAND $<TARGET_FILE:${TARGET_NAME}_a>)
add_test(NAME ${TARGET_NAME}_b COMMAND $<TARGET_FILE:${TARGET_NAME}_b>)

add_custom_target(BuildTest COMMAND "ctest" "-V" "-C" $<CONFIG>)
add_dependencies(BuildTest ${TARGET_NAME}_a)
add_dependencies(BuildTest ${TARGET_NAME}_b)
[/text]

2-2-5.実行結果
result_bL.log
[test] LoadOnly:1
{
“SerialzierName”:”JsonTheolizer”,
“GlobalVersionNo”:1,
“TypeInfoList”:[1] }
“eFoo0”
“eFoo1”
{
“mBar3”:0,
“mBar2″:”12”,
“mBar1”:11
}
(eNone=0, eFoo0=1000, eFoo1=1001, eFoo2=1002)
aFooA=1000
aFooB=1001
aBar.mBar1=11
aBar.mBar2=12
aBar.mBar3=0
[/test]

2-3.変更後のプログラムで自分のプログラムのデータを読む

もちろん、変更後のプログラムは自分が保存したデータを読めます。
以下はmain_b.exeをパラメータ無しで起動し、自分が保存したデータを自分で回復した時の結果です。

2-3-1.保存されたファイル
test.json
[test] {
“SerialzierName”:”JsonTheolizer”,
“GlobalVersionNo”:1,
“TypeInfoList”:[1] }
“eFoo1”
“eFoo2”
{
“mBar3”:103,
“mBar2″:”102”,
“mBar1”:101
}
[/test]
2-3-2.実行結果
result_b.log
[test] LoadOnly:0
{
“SerialzierName”:”JsonTheolizer”,
“GlobalVersionNo”:1,
“TypeInfoList”:[1] }
“eFoo1”
“eFoo2”
{
“mBar3”:103,
“mBar2″:”102”,
“mBar1”:101
}
(eNone=0, eFoo0=1000, eFoo1=1001, eFoo2=1002)
aFooA=1001
aFooB=1002
aBar.mBar1=101
aBar.mBar2=102
aBar.mBar3=103
[/test]

3.まとめ

このようにメンバの追加/変更程度であれば、比較的お手軽に変更できます。
enum型については、更にシンボル名やシンボル値を変更した時の追従機能もありますが、それはまた別の機会に説明したいと思います。