Theolizer  Version.1.1.3
serializer for C++ / Do you want to update your classes easily ?
クラスのアップデート/バージョン・アップ方法

ここでは、クラス(classとstruct)を修正した時、古いプログラムが保存したデータを回復するための各種指定方法を説明します。


1.バージョン番号を変えないでクラスを修正

クラスのバージョン番号の更新無しで行う修正は、enum型と異なりTHEOLIZER_ANNOTATE()を用いて指定することは特にありません。通常通りクラス定義を変更するだけです。

その際、1-2-1.クラスについて で説明した「名前対応」と「順序対応」でできることが異なります。
また、Theolizerは考え方としては配列も一種のクラス(複数の変数の集まり)として取り扱います。
例えば2次元配列は1次元配列の配列とします。これはC++の考え方に準じます。


1-1.名前対応の場合

メンバ変数のメモリ上のデータとシリアライズ・データ間を変数名で対応しますので、柔軟な対応が可能です。

  • メンバ変数の追加
    メンバ変数を任意の位置へ追加できます。古いシリアライズ・データを回復する時、新規追加したメンバ変数の値は変更せず、そのまま維持します。ただし、オーナー指定ポインタへ回復する際に領域を獲得した時はコンストラクタにて初期化された値となります。

  • メンバ変数の削除
    任意の位置のメンバ変数を削除できます。古いシリアライズ・データには削除したメンバ変数に対応するデータが含まれていますが、そのデータは破棄されます。

  • メンバ変数の順序変更
    メンバ変数の定義順序の変更が可能です。メンバ変数名で対応付けて正しい変数へ回復します。

  • 基底クラスについて
    基底クラスの追加/削除/順序変更はメンバ変数の変更に準じます。
    なお、基底クラスは"{基底クラス名}"と言う名前でシリアライス・データへ記録します。

注意事項:
enum型はバージョン・アップ無しでのシンボル名/値変更に対応していましたが、メンバ変数名を変更する時はバージョン・アップが必要です。原理的には対応可能ですので要望があれば対応します。


1-2.順序対応の場合

メンバ変数のメモリ上のデータとシリアライズ・データ間を双方の順序で対応します。例えば、先頭のメンバ変数はシリアライズ・データ先頭から回復されます。そのため、変更対応はあまりできません。

  • メンバ変数の追加
    メンバ変数の並びの最後へのみ追加できます。古いシリアライズ・データを回復する時、新規追加したメンバ変数の値は変更せず、そのまま維持します。ただし、オーナー指定ポインタへ回復する際に領域を獲得した時はコンストラクタにて初期化された値となります。
    メンバ変数の削除と併用するのは危険です。順序だけで対応するため、シリアライズ・データに記録された旧メンバ変数を新メンバ変数へ回復しようとします。どうしても必要な場合はバージョン番号変更により対応して下さい。

  • メンバ変数の削除
    メンバ変数の並びの最後からのみ削除できます。(途中を削除することはできません。)古いシリアライズ・データには削除したメンバ変数に対応するデータが含まれていますが、そのデータは破棄されます。
    メンバ変数の追加と同様、メンバ変数の削除を追加と併用するのは危険です。

  • メンバ変数の順序変更
    頭から順に回復しますので、定義順序の変更には対応できません。

  • 基底クラスについて
    基底クラスの追加/削除/順序変更はメンバ変数の変更に準じます。


1-3.配列の場合

  • 考え方
    配列は、順序対応に準じた考え方です。

  • 要素数の増加
    要素を増やすと、従来の要素列の最後に追加されます。古いシリアライズ・データを回復する時、新規追加された要素の値は変更せず、そのまま維持します。

  • 要素数の減少
    要素を減らすと、従来の要素列の際がから削除されます。古いシリアライズ・データには削除された要素に対応するデータが含まれていますが、そのデータは破棄されます。

  • 配列の要素数の上限
    バージョン番号変更時に要素毎の引き継ぎが必要です。それをコンパイル時に再帰関数を用いて処理していますので、あまり大きな配列に対応できません。
    現在、プリビルド版を提供している環境では要素数はsource/reference_and_test/all_common.hのkArrayMaxでテストしています。(v0.5.0では160)


1-4.違反した場

例えば、順序対応において変数の並び順を変更した場合、シリアライズ・データ内の変数と異なる変数へデータを回復しようとします。もしも、同じ型の変数であった場合はそのまま回復してしまいます。もしも、異なる型の変数だった場合、2つの事態があります。

  • シリアライザのコンストラクト時にTypeCheckかTypeCheckByIndexを指定した場合
    この場合は型チェックを行います。互換性のない型から回復しようとした場合、エラーになります。
  • それ以外の場合
    異常動作します。多くの場合はフォーマット・エラーが発生しますが、エラーになることを保証できません。


1-5.サンプル・ソース

1-1-1.名前対応クラス


変更前のソース例:(source/reference_and_test/ver1a/test_modify_class.h)

struct ModifyClassName :
// --- 基底クラス ---
public ModifyFullAuto,
public ModifyHalfAuto,
public ModifyManual
{
// --- クラス型メンバ変数 ---
ModifyFullAuto mFullAutoMember;
ModifyHalfAuto mHalfAutoMember;
ModifyManual mManualMember;
// --- 基本型メンバ変数 ---
short mShort;
int mInt;
unsigned mUnsigned;
// 以下略
};


変更後のソース例:(source/reference_and_test/ver1b/test_modify_class.h)

struct ModifyClassName :
// --- 基底クラス ---
public ModifyManual, // 1b:順序変更
public ModifyFullAuto,
// public ModifyHalfAuto, // 1b:削除
public ModifyHalfAutoX // 1b:追加
{
// --- クラス型メンバ変数 ---
ModifyManual mManualMember; // 1b:順序変更
ModifyFullAuto mFullAutoMember;
// ModifyHalfAuto mHalfAutoMember; // 1b:削除
ModifyHalfAutoX mHalfAutoXMember; // 1b:追加
// --- 基本型メンバ変数 ---
unsigned mUnsigned; // 1b:順序変更
short mShort;
// int mInt; // 1b:削除
long mLong; // 1b:追加
// 以下略
};


変更前(ver1a)にシリアライズされたModifyClassNameのデータ(該当データのみ抽出):

{
"{ModifyFullAuto}":{
"mFullAuto":100
},
"{ModifyHalfAuto}":{
"mHalfAuto":101
},
"{ModifyManual}":[
102
],
"mFullAutoMember":{
"mFullAuto":110
},
"mHalfAutoMember":{
"mHalfAuto":111
},
"mManualMember":[
112
],
"mShort":120,
"mInt":121,
"mUnsigned":122
}


それを変更後のプログラムで回復してJsonで出力:

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
{
"{ModifyManual}":[
102
],
"{ModifyFullAuto}":{
"mFullAuto":100
},
"{ModifyHalfAutoX}":{
"mHalfAutoX":""
},
"mManualMember":[
112
],
"mFullAutoMember":{
"mFullAuto":110
},
"mHalfAutoXMember":{
"mHalfAutoX":""
},
"mUnsigned":122,
"mShort":120,
"mLong":0
}

下記のように回復されます。

  • ver1aとver1bの両方に存在するデータ
    これらはそのまま回復されます。基底クラスはクラス名、メンバ変数は変数名で対応しますので、定義順序を変更しても回復できます。

  • 新規追加されたModifyHalfAutoX基底クラスとmHalfAutoXMemberとmLongメンバ変数
    ver1aのシリアライズ・データには存在しませんので、シリアライズ前の値が維持されます。
    サンプル・ソースでは0もしくは""(長さ0の文字列)でクリア後にデシリアライズしていますので0、""になっています。
    また、オーナー指定ポインタ経由で保存され、回復時にコンストラクトされた場合は、コンストラクタで設定した値が維持されます。

  • ver1bで削除された基底クラスModifyHalfAutoとメンバ変数mHalfAutoMember
    これらはシリアライズ・データ内に記録されていますが、回復先がありませんので破棄されます。

ちなみに下記コードでJsonフォーマットで標準出力へ出力したものです。

{
THEOLIZER_PROCESS(jos, aModifyClassName);
}


1-5-2.順序対応クラス


変更前のソース例:(source/reference_and_test/ver1a/test_modify_class.h)

struct ModifyClassOrder :
// --- 基底クラス ---
public ModifyFullAuto,
public ModifyHalfAuto,
public ModifyManual
{
// --- クラス型メンバ変数 ---
ModifyFullAuto mFullAutoMember;
ModifyHalfAuto mHalfAutoMember;
ModifyManual mManualMember;
// --- 基本型メンバ変数 ---
short mShort;
int mInt;
unsigned mUnsigned;
// 以下略
};


変更後のソース例:(source/reference_and_test/ver1b/test_modify_class.h)

struct ModifyClassOrder :
// --- 基底クラス ---
public ModifyFullAuto,
public ModifyHalfAuto,
public ModifyManual
{
// --- クラス型メンバ変数 ---
ModifyFullAuto mFullAutoMember;
ModifyHalfAuto mHalfAutoMember;
ModifyManual mManualMember;
// --- 基本型メンバ変数 ---
short mShort;
int mInt;
unsigned mUnsigned;
// 1b:追加
ModifyHalfAutoX mHalfAutoXMember;
long mLong;
// 以下略
};


変更前(ver1a)のプログラムで保存されたModifyClassOrderのデータ(該当データのみ抽出):

[
{
"mFullAuto":200
},
{
"mHalfAuto":201
},
[
202
],
{
"mFullAuto":210
},
{
"mHalfAuto":211
},
[
212
],
220,
221,
222
]


上記データを変更後のプログラムで回復してJson形式で出力:

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
[
{
"mFullAuto":200
},
{
"mHalfAuto":201
},
[
202
],
{
"mFullAuto":210
},
{
"mHalfAuto":211
},
[
212
],
220,
221,
222,
{
"mHalfAutoX":""
},
0
]

新規追加されたmHalfAutoXMemberとmLongメンバ変数はシリアライズ前のデータが維持されます。
同じバージョン内で可能な変更は、最後への追加、もしくは最後からの削除のどちらか一方のみですので、今回は追加してます。削除した場合は当該シリアライズ・データは単純に破棄されます。
基底クラス→メンバ変数の順序でシリアライズするため、メンバ変数が無い場合は、最後の基底クラスの後への基底クラスの追加、もしくは、最後の方からの基底クラスの削除のどちらかが可能です。

ちなみに下記コードでJsonフォーマットで標準出力へ出力しています。

THEOLIZER_PROCESS(jos, aModifyClassOrder);


1-5-3.配列の要素数の変更サンプル


変更前のソース例:(source/reference_and_test/ver1a/test_modify_class.h)

struct ArrayTest
{
static const unsigned kSizeA=2;
static const unsigned kSizeB=3;
unsigned mArray1D[kSizeA];
unsigned mArray2D[kSizeA][kSizeB];
unsigned mArray3D[kSizeA][kSizeB][kSizeA];
// 以下略
};


変更後のソース例:(source/reference_and_test/ver1b/test_modify_class.h)

struct ArrayTest
{
static const unsigned kSizeA=3;
static const unsigned kSizeB=2;
unsigned mArray1D[kSizeA];
unsigned mArray2D[kSizeA][kSizeB];
unsigned mArray3D[kSizeA][kSizeB][kSizeA];
// 以下略
};


変更前(ver1a)のプログラムで保存されたModifyClassOrderのデータ(該当データのみ抽出):
(長いのでmArray3Dを省略)

[
[1,"ArrayTest",{
"mArray1D":[
0,
1
],
"mArray2D":[
[
0,
1,
2 //★ver1bでは破棄
],
[
1000,
1001,
1002 //★ver1bでは破棄
]
],
"mArray3D":[
省略
]
}]
]


上記データを変更後のプログラムで回復してJson形式で出力:
(長いのでmArray3Dを省略)

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
[
[1,"ArrayTest",{
"mArray1D":[
0,
1,
0 //★未変更
],
"mArray2D":[
[
0,
1
],
[
1000,
1001
],
[
0, //★未変更
0 //★未変更
]
],
"mArray3D":[
省略
]
}]
]

ver1bにあってver1aにない要素の内容は、ver1bで生成された時の0のまま変更されていません。
また、ver1aになってver1bにない要素は破棄されています。


2.グローバル・バージョン番号テーブル生成

クラスについてもバージョン・アップする際にはグローバル・バージョン番号テーブルが必要です。
enum型の場合と同じですので、詳しくは2.グローバル・バージョン番号テーブル生成 を参照下さい。


3.バージョン番号を変えることで可能な修正

enum型と同様に各クラスへバージョン番号を割り当てることで大きな修正を行うことができます。

3-1.名前対応の場合

名前対応クラスは下記の変更に対応できます。

  • メンバ変数、および、基底クラスの追加/削除/順序変更
    これらに付いてはバージョン番号を変えない場合と同じです。

  • メンバ変数名の変更
    型を変更せず名前だけの変更であれば参照できますので、変更可能です。THEOLIZER_ANNOTATE()で指定します。

  • クラス名の変更
    次の場合、シリアライズ・データ中に型名が記録されていますので、クラス名を変更すると通常は型チェックに通りません。
    しかし、適切にリファクタリングすればクラス名の変更も可能です。
    • 型チェック有り(TypeCheckByかTypeCheckByIndexを指定して派生シリアライザを生成した時)
    • クラスへのポインタをオーナー指定している時(ポリモーフィズム対応のため)

  • 順序対応への変更
    クラス定義が枯れてきたら効率の良い順序対応へ変更できます。

3-2.順序対応の場合

順序対応クラスは下記の変更に対応できます。

  • メンバ変数、および、基底クラスの追加/削除/順序変更
    これらに付いてはバージョン番号を変えない場合と同じです。

  • メンバ変数名の変更とクラス名の変更
    名前対応の場合と同じです。

  • 名前対応への変更
    クラスの大幅変更が必要になった時に一度名前対応へ変更すると効率良く開発できます。

3-3.配列の場合

配列は下記の変更に対応できます。なお、基底の型はクラスに限りません。プリミティブやenum型を含む任意の型に対応しています。

  • 考え方
    配列は、順序対応に準じた考え方です。 配列の要素数はバージョン番号変更無しで変更できますが、次元数はバージョン変更時にのみ可能です。ただし、配列単独ではバージョン番号を管理できる仕組みに対応していませんので、クラス・メンバとしての配列のみ次元数変更が可能です。

  • 要素数の増加/減少
    これらに付いてはバージョン番号を変えない場合と同じです。

  • 次元数の増加
    最上位の次元へ追加されます。対応先がないものは無視されます。
    例えば、int array[2][3];をint array[2][3][2];と増加した場合、次のように対応します。
    変更前変更後
    array[0][0]array[0][0][0]
    array[0][1]array[0][0][1]
    array[0][2]
    array[1][0]array[0][1][0]
    array[1][1]array[0][1][1]
    array[1][2]
    array[0][2][0]
    array[0][2][1]
    array[1][0][0]
    array[1][0][1]
    array[1][1][0]
    array[1][1][1]
    array[1][2][0]
    array[1][2][1]

  • 次元数の減少
    最上位の次元から削除されます。対応先がないものは無視されます。
    例えば、int array[2][2][3];をint array[3][2];と増加した場合、次のように対応します。
    変更前変更後
    array[0][0][0]array[0][0]
    array[0][0][1]array[0][1]
    array[0][2]
    array[0][1][0]array[1][0]
    array[0][1][1]array[1][1]
    array[1][2]
    array[0][0][2]
    array[0][1][2]
    array[1][0][0]
    array[1][0][1]
    array[1][0][2]
    array[1][1][0]
    array[1][1][1]
    array[1][1][2]


4.バージョン番号を変えてクラス修正する仕組み概要

4-1.コア・データ構造

バージョン毎にシリアライズ対象のメンバ変数だけを集めたクラス(TheolizerVersion)を定義しています。これは、当該バージョンで持っていたメンバ変数を管理する内部クラスです。

各バージョンのTheolizerVersionは、次バージョンのTheolizerバージョンとの関係を管理します。バージョンnのTheolzierVersionの各メンバ変数には、バージョンn+1のTheolizerバージョンのどのメンバ変数と対応するのか記録され、自動的に引継ぎ処理が実行されます。

また、この自動引継ぎ処理は、メンバ変数のアドレスも引継ぎますので、旧バージョンでオブジェクト追跡されていた情報も回復できます。メンバ変数名を変更してもアドレス引継ぎは機能します。ただし、メンバ変数の型を変更した場合は、アドレスを引き継げません。

重要事項:
Theolizerドライバの主な役割はTheolizerVersionを自動生成することであり、*.theolzier.hppファイルには当該コンパイル単位でシリアライズ指定されているクラスとenum型のTheolizerVersionの定義が生成されます。旧バージョンのTheolizerVersion定義もここに入っていますので、*.theolzier.hppは自動生成ファイルですがバージョン管理システムの管理対象ソースとして登録して下さい。

4-2.補助データ構造

3-3.クラス・メンバ変数のKeep-step で説明したように、足並みを揃えて(Keep-step)バージョン・アップ/ダウンする変数としない変数があります。

Keep-step処理する基底クラスやメンバ変数は、4-1.で説明したTheolizerVersionクラスにて管理されます。
Keep-step処理しない(Non-keep-step)基底クラスやメンバ変数用には異なるクラスで管理しています。

  • Non-keep-step基底クラス(非侵入型手動クラス)
    TheolizerBase<基底クラス>クラスを用いて、シリアライズ対象クラス・インスタンスへの参照を管理しています。

  • Non-keep-stepメンバ変数
    TheolizerNonKeepStep<型>クラスを用いています。

更にTheolizerNonKeepStep<型>クラスは下記のような2種類を定義しています。

  • 部分特殊化 : プリミティブ型(int型やstd::string型)
  • プライマリ : その他のNon-keep-step全て(ポインタ、参照、非侵入型手動クラス)

4-3.侵入型半自動におけるバージョン・アップ/ダウン処理

  • 旧バージョンのデータを保存する時の内部動作
    ターゲットのユーザ定義クラスから最新版のTheolizerVersionを生成後、次々とカスケードに1つ前のバージョンのTheolizerVersionをコンストラクトする際にメンバ変数を引き継ぎつつ、目的のバージョンのTheolizerVersionまで生成します。そして、そのバージョンのTheolizerVersionを使って保存処理を行います。
    この時、1つ前のバージョンのTheolizerVersionを生成後、ユーザ定義のバージョン・ダウン処理(downVersion関数)が定義されていたら、それを呼び出します。

  • 旧バージョンのデータから回復する時の内部動作
    保存時と同様の流れで、目的のハージョンのTheolizerVersionまで生成します。目的のTheolizerVersionを使って回復処理を行い、生成時と逆の順序でTheolizerVersionをデストラクトしつつ、ターゲットのユーザ定義クラスへ値を戻します。
    この時、1つ前バージョンのTheolizerVersionをデストラクトする時に、ユーザ定義のバージョン・アップ処理(upVersion関数)が定義されていたら、それを呼び出します。

4-4.非侵入型手動におけるバージョン・アップ/ダウン処理

こちらは半自動型と異なり、カスケードにバージョン・アップ/ダウンを行うことはたいへん難しく、却ってあなたのプログラム開発の手間を増大させるため、カスケード処理は行いません。従って、各バージョン毎に保存処理と回復処理を記述する必要があります。
しかし、対象クラスを変更する度にバージョン・アップが必要なわけではありません。対象クラスを回復する際に従来の保存データでは不足する場合にのみバージョン・アップが必要になります。

また、バージョン変更の際にシリアライズ対象クラスと保存/回復処理間のI/Fに変化がなければ、旧バージョンの保存/回復処理の変更は不要です。しかし、もし、このI/Fを変更された場合は、それに合わせて旧バージョンの保存/回復処理の修正も必要となります。ですので、他のクラスとのI/Fは自由ですが、保存/回復処理とのI/Fはできるだけ変更しないことをお勧めします。

4-5.バージョン・アップ時の注意事項

3.クラスとenum型の変更対応について にて記述した以下の点にご注意下さい。

  1. down/upVersion関数で変更可能な変数
    プリミティブ型とenum型変数のみです。
    クラス型メンバ変数にに含まれるプリミティブ型とenum型変数も変更可能です。更に、クラス型メンバ変数にに含まれるクラス型メンバ変数に含まれるプリミティブ型とenum型変数も変更可能です。(以下同様)

  2. それ以外のものは変更してはいけません。
    3-4-3.down/upVersion関数で変更可能な変数と変更してはいけない変数のまとめ 参照
    • ポインタ、および、参照が指す先のインスタンス
    • 非侵入型手動クラスの基底クラス、および、メンバ変数
    • ポインタそれ自身

  3. upVersionで参照する変数は先に回復すること
    クラス分割により、異なるシリアライズ・データから回復される変数が存在します。
    それらをアクセスしてupVersion処理を行う場合、依存先のメンバ変数を先に回復する必要があります。
    見落とし易いのでご注意下さい。


5.バージョン番号を変えてクラスを修正する方法


5-1.メンバ変数名の変更

リファクタリングの際にメンバ変数名を変更したい場合があると思います。
単に変更するだけの場合、名前を変更した変数は削除して、別名の変数を追加された扱いとなり、変更前のプログラムで保存したシリアライズ・データから回復する際に、値を引き継ぐことができません。

メンバ変数名を変更するにはTHEOLIZER_ANNOTATE(FS:)の第一パラメータを用いて対応つけます。
変更後のクラス定義で、変更前の変数名を指定します。

型 メンバ変数名 THEOLIZER_ANNOTATE(FS:旧メンバ変数名);

以下、名前対応クラス(ModifyClassName)と順序対応クラス(ModifyClassOrder)のmHalfAutoXMemberメンバ変数の変数名をmHalfAutoYMemberへ変更する例です。どちらのクラスの場合も同じです。
なお、型変更には対応していません。変数名を変更して対応付ける各変数の型は同じにして下さい。

なお、下記の例ではクラス名を変更していますが、クラス自体は同じものです。
クラス名をModifyHalfAutoXからModifyHalfAutoYへ変更しています。クラス名の変更については次節で説明します。


変更前のソース例:(source/reference_and_test/ver1c/test_modify_class.h)

struct ModifyClassName :
// --- 基底クラス ---
public ModifyManual, // 1b:順序変更
public ModifyFullAuto,
// public ModifyHalfAuto, // 1b:削除
public ModifyHalfAutoX, // 1b:追加
public OldFullAuto0 // 1c:追加(2aで削除)
{
// --- クラス型メンバ変数 ---
ModifyManual mManualMember; // 1b:順序変更
ModifyFullAuto mFullAutoMember;
// ModifyHalfAuto mHalfAutoMember; // 1b:削除
ModifyHalfAutoX mHalfAutoXMember; // 1b:追加
// クラス型メンバ変数削除テスト
OldFullAuto1 mOldFullAuto1; // 1c:追加(2aで削除)
// enum型メンバ変数削除テスト
OldFullAutoEnum mOldFullAutoEnum; // 1c:追加(2aで削除)
// --- 基本型メンバ変数 ---
unsigned mUnsigned; // 1b:順序変更
short mShort;
// int mInt; // 1b:削除
long mLong; // 1b:追加
// 以下略
THEOLIZER_INTRUSIVE(CS, (ModifyClassName), 1); // 侵入型半自動へ変更後、バージョン・アップする
};

struct ModifyClassOrder :
// --- 基底クラス ---
public ModifyFullAuto,
public ModifyHalfAuto,
public ModifyManual
{
// --- クラス型メンバ変数 ---
ModifyFullAuto mFullAutoMember;
ModifyHalfAuto mHalfAutoMember;
ModifyManual mManualMember;
// --- 基本型メンバ変数 ---
short mShort;
int mInt;
unsigned mUnsigned;
// 1b:追加
ModifyHalfAutoX mHalfAutoXMember;
long mLong;
// 以下略
THEOLIZER_INTRUSIVE_ORDER(CS, (ModifyClassOrder), 1);
};


変更後のソース例:(source/reference_and_test/ver2a/test_modify_class.h)

struct ModifyClassName :
// --- 基底クラス ---
public ModifyManual, // 1b:順序変更
public ModifyFullAuto,
// public ModifyHalfAuto, // 1b:削除
public ModifyHalfAutoY // 1b:追加→2a:名前変更
{
// --- クラス型メンバ変数 ---
ModifyManual mManualMember; // 1b:順序変更
ModifyFullAuto mFullAutoMember;
// ModifyHalfAuto mHalfAutoMember; // 1b:削除
ModifyHalfAutoY mHalfAutoYMember THEOLIZER_ANNOTATE(FS:mHalfAutoXMember);// 1b:追加→2a:名変更
// --- 基本型メンバ変数 ---
unsigned mUnsigned; // 1b:順序変更
short mShort;
// int mInt; // 1b:削除
long mLong; // 1b:追加
// --- クラス配列型メンバ変数 ---
ModifyFullAuto mFullAutoArray[2]; // 2b:追加(3a:削除)
ModifyHalfAuto mHalfAutoArray[2]; // 2b:追加(3a:削除)
ModifyManual mManualArray[2]; // 2b:追加(3a:削除)
// 以下略
THEOLIZER_INTRUSIVE(CS, (ModifyClassName), 2); // ver.2へバージョン・アップ
};

struct ModifyClassOrder :
// --- 基底クラス ---
public ModifyFullAuto,
public ModifyHalfAuto,
public ModifyManual
{
// --- クラス型メンバ変数 ---
ModifyFullAuto mFullAutoMember;
ModifyHalfAuto mHalfAutoMember;
ModifyManual mManualMember;
// --- 基本型メンバ変数 ---
short mShort;
int mInt;
unsigned mUnsigned;
// 1b:追加
ModifyHalfAutoY mHalfAutoYMember THEOLIZER_ANNOTATE(FS:mHalfAutoXMember);// 2a:名変更
long mLong;
// 以下略
THEOLIZER_INTRUSIVE_ORDER(CS, (ModifyClassOrder), 2); // ver.2へバージョン・アップ
};


5-2.クラス名の変更

リファクタリングを行う時、クラス名を変更したい場合もあると思います。
基本的には通常通り全てのソース・ファイルで使っているクラス名を変更します。
その際、*.theolizer.hppで指定されているクラス名も変更します。ただし、旧バージョンのシリアライズ・データと対応付けるためのクラス名は変更してはいけません。
前者(リファクタリング対象)は(クラス名)のように()で括られています。後者(変更しないクラス名)はu8"クラス名"のように文字列として定義されていますので、(クラス名)を一括修正することで*.theolizer.hppを変更できます。

例えば、下記は「5-1.メンバ変数名の変更」で、クラス名をModifyHalfAutoXからModifyHalfAutoYへ変更する前の*.theolizer.hppファイル(複数あるので注意)の該当部分の一部です。

ModifyHalfAutoX自動生成ソース部

#ifdef THEOLIZER_WRITE_CODE // ###### ModifyHalfAutoX ######

(中略)

#define THEOLIZER_GENERATED_LAST_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kLastVersionNo,1)
#define THEOLIZER_GENERATED_CLASS_TYPE THEOLIZER_INTERNAL_UNPAREN(ModifyHalfAutoX)``

// ---<<< Version.1 >>>---

#define THEOLIZER_GENERATED_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kVersionNo,1)
#define THEOLIZER_GENERATED_CLASS_NAME()\
  THEOLIZER_INTERNAL_CLASS_NAME((u8"ModifyHalfAutoX"))
#define THEOLIZER_GENERATED_ELEMENT_MAP emName
#define THEOLIZER_GENERATED_ELEMENT_LIST()\
  THEOLIZER_INTERNAL_ELEMENT_N((mHalfAutoX),mHalfAutoX,etmDefault,\
  (theolizerD::All),\
  (std::string))<br> #include <theolizer/internal/version_auto.inc><br> #undef THEOLIZER_GENERATED_VERSION_NO<br>
#endif //THEOLIZER_WRITE_CODE // ###### ModifyHalfAutoX ######<br>

ModifyClassName自動生成ソース部

#ifdef THEOLIZER_WRITE_CODE // ###### ModifyClassName ######

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

// ---<<< Version.1 >>>---

#define THEOLIZER_GENERATED_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kVersionNo,1)
#define THEOLIZER_GENERATED_CLASS_NAME()\
  THEOLIZER_INTERNAL_CLASS_NAME((u8"ModifyClassName"))
#define THEOLIZER_GENERATED_ELEMENT_MAP emName
#define THEOLIZER_GENERATED_BASE_LIST()\
  THEOLIZER_INTERNAL_BASE_N(public,etmDefault,0,(ModifyManual),u8"ModifyManual")\
  THEOLIZER_GENERATED_SEP\
  THEOLIZER_INTERNAL_BASE_KN(public,etmDefault,1,(ModifyFullAuto),1,u8"ModifyFullAuto")\
  THEOLIZER_GENERATED_SEP`
  THEOLIZER_INTERNAL_BASE_KI(public,etmDefault,2,(ModifyHalfAutoX),1,u8"ModifyHalfAutoX")
#define THEOLIZER_GENERATED_ELEMENT_LIST()\
  THEOLIZER_INTERNAL_ELEMENT_N((mManualMember),mManualMember,etmDefault,\
  (theolizerD::All),\
  (ModifyManual))\
  THEOLIZER_INTERNAL_ELEMENT_KN((mFullAutoMember),mFullAutoMember,etmDefault,\
  (theolizerD::All),\
  (ModifyFullAuto),1)\
  THEOLIZER_INTERNAL_ELEMENT_KI((mHalfAutoXMember),mHalfAutoXMember,etmDefault,\
  (theolizerD::All),\
 (ModifyHalfAutoX),1)
  THEOLIZER_INTERNAL_ELEMENT_N((mUnsigned),mUnsigned,etmDefault,\
  (theolizerD::All),\
  (unsigned int))\
  THEOLIZER_INTERNAL_ELEMENT_N((mShort),mShort,etmDefault,\
  (theolizerD::All),\
  (short))\
  THEOLIZER_INTERNAL_ELEMENT_N((mLong),mLong,etmDefault,\
  (theolizerD::All),\
  (long))
#include <theolizer/internal/version_auto.inc>
#undef THEOLIZER_GENERATED_VERSION_NO

#endif//THEOLIZER_WRITE_CODE // ###### ModifyClassName ######

ModifyHalfAutoXクラスの名前をModifyHalfAutoYへリファクタリングする際に*.theolizer.hppファイル内のu8"ModifyHalfAutoX"を除く(ModifyHalfAutoX)を(ModifyHalfAutoY)へ変更します。

上記の場合、赤く記した3箇所の(ModifyHalfAutoX)を(ModifyHalfAutoY)へ変更することになります。
なお、コメントに含まれるModifyHalfAutoXは変更してもしなくても影響はありません。


5-3.配列の次元数の変更

バージョン変更する際に配列の次元を増やしたり減らしたりすることができます。
その際、特別な記述は不要です。


変更前のソース例:(source/reference_and_test/ver1c/test_modify_class.h)

struct ArrayTest
{
static const unsigned kSize1=2;
static const unsigned kSize0=3;
unsigned mArrayDim[kSize1][kSize0];
// 以下略
};


変更後のソース例:(source/reference_and_test/ver2a/test_modify_class.h)

struct ArrayTest
{
static const unsigned kSize2=2;
static const unsigned kSize1=3;
static const unsigned kSize0=2;
unsigned mArrayDim[kSize2][kSize1][kSize0];
// 以下略
};


5-4.バージョン・アップ/ダウン処理の記述方法

手順としては以下の通りです。

  1. 通常通り、グローバル・バージョン番号テーブルを定義
    既に定義していたら、再定義は不要です。
    THEOLIZER_DEFINE_GLOBAL_VERSION_TABLE()マクロで定義します。
    詳細は2.グローバル・バージョン番号テーブル生成 を参照下さい。

  2. 対象のクラスが非侵入型完全自動クラスなら、侵入型半自動へ変更して、一度ビルド
    通常はTHEOLIZER_INTRUSIVE()マクロを対象クラス内に記述することで「侵入型半自動」であることを指定します。
    詳細は2.侵入型半自動クラス を参照下さい。
    ここでビルドしてver.1の自動生成ソースを生成します。
    ビルドしない場合、突然ver.2へ上げられたことになり、コンパイルできない場合があります。

  3. バージョン番号をインクリメント
  4. down/upVersion関数雛形をコピー&ペースト
    後述します。

  5. 必要に応じてdown/upVersion関数の内容を記述
    後述します。

5-4-1.down/upVersion関数雛形のコピー&ペースト

シリアライズ対象クラスのシリアライズ用のコードは*.theolizer.hppに自動生成されています。それと一緒にdown/upVersion関数の雛形も定義していますので、それをコピー&ペーストして下さい。

例えば、以下の通りです。


対象クラスKeepStepTest:(source/reference_and_test/ver1c/test_modify_complex.h)

struct KeepStepTest :
public VersionUpDownTest, // Keep-step
public VersionFullAuto, // FullAuto
public VersionManual // Manual
{
VersionUpDownTest mVersionUpDownTest; // Keep-step
VersionUpDownTest* mVersionUpDownTestPtr; // Non-Keep-step
VersionUpDownTest& mVersionUpDownTestRef THEOLIZER_ANNOTATE(FS:<>Pointee); // Non-Keep-step
VersionFullAuto mVersionFullAuto; // FullAuto
VersionManual mVersionManual; // Manual
KeepStepTest(VersionUpDownTest& iVersionUpDownTest, bool iValued=false) :
VersionUpDownTest((iValued)?ClassKind::Kind1:ClassKind::Default),
VersionFullAuto(iValued),
VersionManual(iValued),
mVersionUpDownTest((iValued)?ClassKind::Kind1:ClassKind::Default),
mVersionUpDownTestPtr((iValued)?&iVersionUpDownTest:nullptr),
mVersionUpDownTestRef(iVersionUpDownTest),
mVersionFullAuto(iValued),
mVersionManual(iValued)
{ }
void check(VersionUpDownTest& iVersionUpDownTest)
{
VersionUpDownTest::check(ClassKind::Kind1);
VersionFullAuto::check();
VersionManual::check();
mVersionUpDownTest.check(ClassKind::Kind1);
THEOLIZER_EQUAL_PTR( mVersionUpDownTestPtr, &iVersionUpDownTest);
THEOLIZER_EQUAL_PTR(&mVersionUpDownTestRef, &iVersionUpDownTest);
mVersionFullAuto.check();
mVersionManual.check();
}
THEOLIZER_INTRUSIVE(CS, (KeepStepTest), 1); // 侵入型半自動へ変更後、バージョン・アップする
};


KeepStepTestの自動生成ソース:(ビルド・フォルダ/reference_and_test/ver1c/test_modify_class.cpp.theolizer.hpp)

#ifdef THEOLIZER_WRITE_CODE // ###### KeepStepTest ######
#if false // Sample of up/downVersion function.
template<class tTheolizerVersion, class tNextVersion>
struct KeepStepTest::TheolizerUserDefine<tTheolizerVersion, tNextVersion, 1>
{
// Members version down.
static void downVersion(tNextVersion const& iNextVersion, tTheolizerVersion& oNowVersion)
{
}
// Members version up.
static void upVersion(tTheolizerVersion const& iNowVersion, tNextVersion& oNextVersion)
{
}
};
#endif // Sample of up/downVersion function.
#define THEOLIZER_GENERATED_LAST_VERSION_NO THEOLIZER_INTERNAL_DEFINE(kLastVersionNo,1)
#define THEOLIZER_GENERATED_CLASS_TYPE THEOLIZER_INTERNAL_UNPAREN(KeepStepTest)
// ---<<< Version.1 >>>---
中略
#endif//THEOLIZER_WRITE_CODE // ###### KeepStepTest ######

この #if false ~ #endif までの間をコピーしてください。コピー先はKeepStepTestクラスの定義の後、自動生成ソースの前ならばどこでも良いです。KeepStepTestの定義直後をお勧めします。

コピー先ソース例:(source/reference_and_test/ver2a/test_modify_complex.h)

struct KeepStepTest :
public VersionUpDownTest, // Keep-step
public VersionFullAuto, // FullAuto
public VersionManual // Manual
{
VersionUpDownTest mVersionUpDownTest; // Keep-step
VersionUpDownTest* mVersionUpDownTestPtr; // Non-Keep-step
VersionUpDownTest& mVersionUpDownTestRef THEOLIZER_ANNOTATE(FS:<>Pointee); // Non-Keep-step
VersionFullAuto mVersionFullAuto; // FullAuto
VersionManual mVersionManual; // Manual
KeepStepTest(VersionUpDownTest& iVersionUpDownTest, bool iValued=false) :
VersionUpDownTest((iValued)?ClassKind::Kind2:ClassKind::Default),
VersionFullAuto(iValued),
VersionManual(iValued),
mVersionUpDownTest((iValued)?ClassKind::Kind2:ClassKind::Default),
mVersionUpDownTestPtr((iValued)?&iVersionUpDownTest:nullptr),
mVersionUpDownTestRef(iVersionUpDownTest),
mVersionFullAuto(iValued),
mVersionManual(iValued)
{ }
void check(VersionUpDownTest& iVersionUpDownTest)
{
VersionUpDownTest::check(ClassKind::Kind2, true);
VersionFullAuto::check();
VersionManual::check();
mVersionUpDownTest.check(ClassKind::Kind2);
THEOLIZER_EQUAL_PTR( mVersionUpDownTestPtr, &iVersionUpDownTest);
THEOLIZER_EQUAL_PTR(&mVersionUpDownTestRef, &iVersionUpDownTest);
mVersionFullAuto.check();
mVersionManual.check();
}
THEOLIZER_INTRUSIVE(CS, (KeepStepTest), 2);
};
// ---<<< バージョン・アップ/ダウン処理 ver1 <-> ver2 >>>---
template<class tTheolizerVersion, class tNextVersion>
struct KeepStepTest::TheolizerUserDefine<tTheolizerVersion, tNextVersion, 1>
{
// Members version down.
static void downVersion(tNextVersion const& iNextVersion, tTheolizerVersion& oNowVersion)
{
}
// Members version up.
static void upVersion(tTheolizerVersion const& iNowVersion, tNextVersion& oNextVersion)
{
}
};

以上のTheolizerUserDefineクラス・テンプレートの最後のテンプレート引数の1がローカル・バージョン番号です。1の時はver.1とver.2の間、2の時はver.2とver.3の間のdown/upVersion処理を定義します。

バージョン・アップ/ダウン処理を記述する必要があるバージョンについてTheolizerUserDefineクラスを定義して下さい。

5-4-2.down/upVersion処理を記述する

static void downVersion(tNextVersion const& iNextVersion, tTheolizerVersion& oNowVersion)関数
iNextVersion引数は次バージョンです。
oNowVersionは現バージョン(テンプレート引数で指定したバージョン)です。
それぞれTheolizerVersionクラス のインスタンスが渡ってきます。
iNextVersionを参照しつつ、oNowVersionの設定可能なメンバ を設定して下さい。

設定例:

oNowVersion.mData0=iNextVersion.mData0-100;

なお、基底クラスは実装上の都合により、メンバ変数としてアクセスする必要が有ります。
クラスAの基底クラスへアクセスする時は、クラス名にTheolizerBaseを連結したメンバ変数を使ってアクセスして下さい。
例えば、次バージョンの基底クラスFooClassのmBarメンバへアクセスする場合、iNextVersion.FooClassTheolizerBase.mBar でアクセスして下さい。

static void upVersion(tTheolizerVersion const& iNowVersion, tNextVersion& oNextVersion)関数
iNowVersionは現バージョン(テンプレート引数で指定したバージョン)です。
tNextVersion引数は次バージョンです。
それぞれTheolizerVersionクラス のインスタンスが渡ってきます。
iNextVersionを参照しつつ、oNowVersionの設定可能なメンバ を設定して下さい。

設定例:

oNextVersion.mData0=iNowVersion.mData0+100;

基底クラスのアクセス方法はdownVersion関数と同じです。

upVersion()関数の設定可能なメンバ変数は、set()関数を持っています。これは例えば、変数の型を変更したような場合に用います。(型変更された変数の自動引継ぎをTheolizerはサポートしていませんので、手動で引継ぎが必要だからです。)
例えば、ver.1ではint型であったmFooメンバ変数を、ver.2でlong型へ変更した場合、下記のような処理を記述して引継ぎして下さい。
この時、set関数の第一パラメータの型は引継ぎ先の型となりますので、この例ではlong型です。

oNextVersion.mFoo.set(iNextVersion.mFoo, iNextVersion.mFoo.getDoSucceed());

getDoSucceed()関数は、そのメンバ変数の引継ぎが必要かどうかを示します。通常はデシリアライズ時に回復された場合trueとなります。また、set(xxx, true)で設定された場合もtrueになります。最終的に最新版のTheolizerVersionでtrueだった場合、その変数の値がシリアライズ対象クラスの該当メンバへ設定されます。
従って、上記の文により、mFooがデシリアライズされていた場合に限り、その値が次バージョンのmFoo変数へ引き継がれます。


6.網羅的な使用例(自動テスト)の説明


6-1.クラス変更のテスト

クラス変更の基本的な網羅的なテストは 4.テスト・プログラムの構造 で説明した各フォルダの下記2つのファイルにて実装しています。

  • test_modify_class.h
  • test_modify_class.cpp

名前対応クラス(ModifyClassName)、順序対応クラス(ModifyClassOrder)、配列メンバを持つクラス(ArrayTest)について下記の修正を行い、4-1-2.テストの組み合わせ の各バージョン間で正しく回復できることを確認しています。
それぞれのメンバ変数の型は、非侵入型完全自動クラス、侵入型半自動クラス、非侵入型手動クラス、および、プリミティブ型をテストしています。

バージョンModifyClassNameModifyClassOrderArrayTest
ver1a→ver1b基底クラスとメンバ変数について
 順序変更、追加、削除
メンバ変数の追加要素数の増加と減少
ver1b→ver1c基底クラス、メンバ変数(クラス、enum)について
 旧バージョンのみのシリアライズ
ver1c→ver2aクラス名変更
クラス型メンバ変数名変更
クラス名変更
クラス型メンバ変数名変更
次元数増加
ver2a→ver3aプリミティブ型メンバ変数名変更
クラス配列型メンバ変数追加
プリミティブ型メンバ変数名変更次元数減少
ver3a→ver3b基底クラスとメンバ変数について
 順序変更、追加、削除
クラス配列型メンバ変数削除
クラス型とプリミティブ型について
 メンバ変数削除
要素数上限


6-2.クラス変更の複合テスト

クラス変更の複合的で網羅的なテストは 4.テスト・プログラムの構造 で説明した各フォルダの下記2つのファイルにて実装しています。

  • test_modify_complex.h
  • test_modify_complex.cpp

ここでは、大きく下記の2種類についてテストしています。

  • オブジェクト追跡している時のバージョン変更
    用いる主なクラスは、ポイントされる側のメンバ変数を持つクラス(PointeeInclude)、ポインタ型メンバ変数を持つクラス(PointerInclude)。
    ポイントされる変数について値だけでなく、アドレスも引き継がれることを確認しています。
    変数名を変更した時に値だけでなく、アドレスも引き継がれることを確認しています。

  • バージョン・アップ/ダウン処理
    用いる主なクラスは、主なdown/upVersionテスト用のVersionUpDownTestクラス、Keep-step変数のテストとNon-keep-step変数用の外側のupVersionテスト用のKeepStepTestクラスです。
    KeepStepTestはVersionUpDownTestを基底クラス、および、メンバ変数として含みます。
    「upVersionの全組合せテスト」では、下記の全組合せをテストします。
    • データ回復した場合、しなかった場合
    • set()しなかった場合、DoSucceed=falseでset()した場合、DoSucceed=trueでset()した場合
    • 代入しなかった場合、代入した場合
バージョンオブジェクト追跡バージョン・アップ/ダウン処理
ver1a→ver1b
ver1b→ver1cテスト準備
ver1c→ver2a被ポインタとポインタ追加Non-keep-step(enum型とプリミティブ型)について
 upVersionの全組合せテスト
ver2a→ver3a変数名変更型変更対応
外側のupVersion()優先
ver3a→ver3b