Theolizer  Version.1.1.3
serializer for C++ / Do you want to update your classes easily ?
保存先指定について

ここでは、保存先指定について説明します。


1.指定方法

1-1.シリアライズの有無指定

クラスや構造体に含まれるメンバ変数をシリアライズするかしないか指定できます。 指定方法は2種類あります。 侵入型半自動クラスについて指定可能です。(*1)

  1. デフォルト保存クラス:メンバ変数をデフォルトでシリアライズするクラス
    シリアライズ指定していないメンバ変数はシリアライズ対象となります。
  2. デフォルト非保存クラス:メンバ変数をデフォルトではシリアライズしないクラス シリアライズ指定していないメンバ変数はシリアライズ対象としません。
(*1)
非侵入型完全自動クラスのメンバ変数への指定も可能ですが、クラス定義ソースの修正が必要であるため、現状では意味がありません(侵入する必要がある)ので、非サポートとします。

1-2.保存先指定

Theolizerは、クラスを複数のファイルや通信先に分割して保存/回復できます。
そのためには

  1. 保存先を定義する
    特殊なenum型を定義するイメージです。
  2. 侵入型半自動クラスのメンバ変数に保存先を指定する。 複数の保存先を指定できます。
  3. シリアライザをコンストラクトする際に保存先を指定する。 通常は保存先を1つ指定しますが、複数指定することも可能です。

これにより、2と3で指定された保存先集合間に共通な保存先がある場合、2のメンバ変数が3で生成したシリアライザに渡したstreamへ保存、streamから回復されます。

なお、両者とも保存先を明示的に指定しなかった場合、「保存先無し」の意味ではなく「全ての保存先」の意味になります。2に保存先を明示的に指定していない場合、3でどの保存先を指定しても常にシリアライズされます。また、3で保存先を明示的に指定していない場合、2にどの保存先を指定していても常にシリアライズされます。

1-3.指定方法

1-3-1.保存先の定義方法

THEOLIZER_DESTINATIONS()マクロで定義します。
通常のenum型の定義と同じく、保存先を用いる場所全てより前に定義して下さい。
構文は次の通りです。

THEOLIZER_DESTINATIONS(前THEOLIZER_DESTINATIONS()マクロ最後の保存先, 保存先0, 保存先1, ... );

マクロ展開の都合上、THEOLIZER_DESTINATIONS()1つで最大8つの保存先を定義できます。8つを超える保存先を定義したい場合、THEOLIZER_DESTINATIONS()を続けて記述して下さい。例えは保存先を12個定義したい場合は、下記のようになります。

THEOLIZER_DESTINATIONS(All, Dest0, Dest1, Dest2, Dest3, Dest4, Dest5, Dest6, Dest7);
THEOLIZER_DESTINATIONS(Dest7, Dest8, Dest9, Dest10, Dest11);

シンボルはAll以外を自由に使って頂いて良いです。文字はenum型のシンボル名に使えるものを全て使えます。
なお、Allは事前定義しています。「全ての保存先」の意味を持ち、内部的に使用しています。

また、各保存先はtheolizer::destination名前空間に配置されます。また、この名前空間にはtheolizerDの別名を定義しています。例えばDest1はtheolizerD::Dest1として指定します。

1-3-2.デフォルト保存/非保存クラスの指定方法

2.侵入型半自動クラス をTHEOLIZER_INTRUSIVE()マクロで指定しますが、その先頭パラメータで指定します。
デフォルト保存クラス :THEOLIZER_INTRUSIVE(CS, (クラス名), バージョン番号);
デフォルト非保存クラス:THEOLIZER_INTRUSIVE(CN, (クラス名), バージョン番号);

1-3-3.メンバ変数への指定方法

メンバ変数への指定にはTHEOLIZER_ANNOTATE()マクロをメンバ変数定義の最後、;の前に書きます。
保存しない:THEOLIZER_ANNOTATE(FN)
保存する :THEOLIZER_ANNOTATE(FS)
保存し、保存先も指定する:THEOLIZER_ANNOTATE(FS:<保存先のリスト>))

1-3-4.シリアライザへの指定方法

シリアライザ名<保存先のリスト> インスタンス名(パラメータ);の構文で指定します。

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

1-4-1.保存の有無指定


デフォルト保存クラス例 (source/reference_and_test/basic2/test_destinations.h)

class DefaultSave
{
int mNoAnnotate;
int mAnnotateSave THEOLIZER_ANNOTATE(FS);
int mAnnotateNonSave THEOLIZER_ANNOTATE(FN);
public:
DefaultSave() : mNoAnnotate(0), mAnnotateSave(0), mAnnotateNonSave(0)
{ }
DefaultSave(bool) : mNoAnnotate(100), mAnnotateSave(200), mAnnotateNonSave(300)
{ }
void check()
{
THEOLIZER_EQUAL(mNoAnnotate, 100);
THEOLIZER_EQUAL(mAnnotateSave, 200);
THEOLIZER_EQUAL(mAnnotateNonSave, 0);
}
// 侵入型半自動 指定
THEOLIZER_INTRUSIVE(CS, (DefaultSave), 1);
};

説明:

メンバ変数指定説明
mNoAnnotate指定無しデフォルト保存クラスなので、シリアライズされます
mAnnotateSave保存するシリアライズされます
mAnnotateNonSave保存しないシリアライズされません

デフォルト非保存クラス例 (source/reference_and_test/basic2/test_destinations.h)

class DefaultNonSave
{
int mNoAnnotate;
int mAnnotateSave THEOLIZER_ANNOTATE(FS);
int mAnnotateNonSave THEOLIZER_ANNOTATE(FN);
public:
DefaultNonSave() : mNoAnnotate(0), mAnnotateSave(0), mAnnotateNonSave(0)
{ }
DefaultNonSave(bool) : mNoAnnotate(1000), mAnnotateSave(2000), mAnnotateNonSave(3000)
{ }
void check()
{
THEOLIZER_EQUAL(mNoAnnotate, 0);
THEOLIZER_EQUAL(mAnnotateSave, 2000);
THEOLIZER_EQUAL(mAnnotateNonSave, 0);
}
// 侵入型半自動 指定
THEOLIZER_INTRUSIVE(CN, (DefaultNonSave), 1);
};

説明:

メンバ変数指定説明
mNoAnnotate指定無しデフォルト非保存クラスなので、シリアライズされません
mAnnotateSave保存するシリアライズされます
mAnnotateNonSave保存しないシリアライズされません


保存と回復ソース:(source/reference_and_test/basic2/test_destinations.cpp)

// ---<<< 保存 >>>---
{
std::ofstream aStream("tutorise_destinations.json");
theolizer::JsonOSerializer<> aSerializer(aStream);
// デフォルト保存
DefaultSave aDefaultSave(true);
THEOLIZER_PROCESS(aSerializer, aDefaultSave);
// デフォルト非保存
DefaultNonSave aDefaultNonSave(true);
THEOLIZER_PROCESS(aSerializer, aDefaultNonSave);
}
// ---<<< 回復 >>>---
{
std::ifstream aStream("tutorise_destinations.json");
theolizer::JsonISerializer<> aSerializer(aStream);
// デフォルト保存
DefaultSave aDefaultSave;
THEOLIZER_PROCESS(aSerializer, aDefaultSave);
aDefaultSave.check();
// デフォルト非保存
DefaultNonSave aDefaultNonSave;
THEOLIZER_PROCESS(aSerializer, aDefaultNonSave);
aDefaultNonSave.check();
}

以上の実行で保存されるファイルは次の通りです。

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
{
"mNoAnnotate":100,
"mAnnotateSave":200
}
{
"mAnnotateSave":2000
}


1-4-2.保存先指定

保存先を指定したクラスをポリモーフィックに保存/回復するサンプルを示します。保存先をマスター・ファイルと取引ファイルへ分割するケースを使います。できるだけ小さなサンプルにしていますので少し違和感があると思いますが、雰囲気は掴めると思います。

なお、このように基底クラスへのポインタから保存/回復することもできますし、普通にクラス・インスタンスを直接保存/回復することもできます。

保存先の定義 (source/reference_and_test/basic/common.h)

(
All,
Master,
Trade
);

クラス例 (source/reference_and_test/basic2/test_destinations.h)

顧客管理を想定してます。BaseCustomerを基底クラスとし、法人の顧客をCorporateCustomer、個人の顧客をIndividualCustomerで管理する想定です。基底クラスで名前を記録し、派生クラスはそれぞれ、資本金と売掛金残高、誕生日と提供したポイント数を記録します。名前と資本金と誕生日はマスター・ファイル、売掛金とポイント数は取引ファイルへ保存します。

// ---<<< 基底クラス >>>---
struct BaseCustomer
{
std::string mName THEOLIZER_ANNOTATE(FS:<theolizerD::Master>); // 名前
BaseCustomer() { }
BaseCustomer(std::string const& iName) :
mName(iName)
{ }
virtual ~BaseCustomer() { }
THEOLIZER_INTRUSIVE(CS, (BaseCustomer), 1);
};
// ---<<< 派生クラス1 >>>---
struct CorporateCustomer : public BaseCustomer
{
int mCapitalStock THEOLIZER_ANNOTATE(FS:<theolizerD::Master>); // 資本金
int mTradeAccounts THEOLIZER_ANNOTATE(FS:<theolizerD::Trade>); // 売掛金
CorporateCustomer() : mCapitalStock(0), mTradeAccounts(0) { }
CorporateCustomer(std::string const& iName, int iCapitalStock, int iTradeAccounts) :
BaseCustomer(iName),
mCapitalStock(iCapitalStock),
mTradeAccounts(iTradeAccounts)
{ }
THEOLIZER_INTRUSIVE(CS, (CorporateCustomer), 1);
};
THEOLIZER_REGISTER_CLASS((CorporateCustomer));
// ---<<< 派生クラス2 >>>---
struct IndividualCustomer : public BaseCustomer
{
std::string mBirthday THEOLIZER_ANNOTATE(FS:<theolizerD::Master>);// 誕生日
int mPoint THEOLIZER_ANNOTATE(FS:<theolizerD::Trade>); // ポイント数
IndividualCustomer() : mPoint(0) { }
IndividualCustomer(std::string const& iName, std::string iBirthday, int iPoint) :
BaseCustomer(iName),
mBirthday(iBirthday),
mPoint(iPoint)
{ }
THEOLIZER_INTRUSIVE(CS, (IndividualCustomer), 1);
};
THEOLIZER_REGISTER_CLASS((IndividualCustomer));

保存ソース:(source/reference_and_test/basic2/test_destinations.cpp)

{
// データ作成
std::list<std::unique_ptr<BaseCustomer> > aList;
aList.emplace_back(new CorporateCustomer("Theoride Technology", 2000000, 500000));
aList.emplace_back(new IndividualCustomer("Yossi Tahara", "1962/01/01", 12000));
// Master保存
std::ofstream aStreamMaster("tutorise_destinationsMaster.json");
theolizer::JsonOSerializer<theolizerD::Master> aSerializerMaster(aStreamMaster);
THEOLIZER_PROCESS(aSerializerMaster, aList);
aSerializerMaster.clearTracking();
// Trade保存
std::ofstream aStreamTrade("tutorise_destinationsTrade.json");
theolizer::JsonOSerializer<theolizerD::Trade> aSerializerTrade(aStreamTrade);
THEOLIZER_PROCESS(aSerializerTrade, aList);
aSerializerTrade.clearTracking();
}

これにて次のように保存されます。

tutorise_destinationsMaster.json:

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
[
2,
[
[1,"CorporateCustomer",{
"(BaseCustomer)":{
"mName":"Theoride Technology"
},
"mCapitalStock":2000000
}]
],
[
[2,"IndividualCustomer",{
"(BaseCustomer)":{
"mName":"Yossi Tahara"
},
"mBirthday":"1962\/01\/01"
}]
]
]

誕生日が\でエスケープされているのは、Jsonの仕様によるものです。

tutorise_destinationsTrade.json:

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
[
2,
[
[1,"CorporateCustomer",{
"(BaseCustomer)":{
},
"mTradeAccounts":500000
}]
],
[
[2,"IndividualCustomer",{
"(BaseCustomer)":{
},
"mPoint":12000
}]
]
]

保存データのまとめ:

クラスメンバ変数内容保存先
CorporateCustomermName"Theoride Technology"マスター・ファイル
mCapitalStock2,000,000マスター・ファイル
mTradeAccounts500,000取引ファイル
IndividualCustomermName"Yossi Tahara"マスター・ファイル
mBirthday1962/01/01マスター・ファイル
mPoint12,000取引ファイル


回復ソース:(source/reference_and_test/basic2/test_destinations.cpp)

{
std::list<std::unique_ptr<BaseCustomer> > aList;
// Master回復
std::ifstream aStreamMaster("tutorise_destinationsMaster.json");
theolizer::JsonISerializer<theolizerD::Master> aSerializerMaster(aStreamMaster);
THEOLIZER_PROCESS(aSerializerMaster, aList);
aSerializerMaster.clearTracking();
// Tradeを合成回復
std::ifstream aStreamTrade("tutorise_destinationsTrade.json");
theolizer::JsonISerializer<theolizerD::Trade> aSerializerTrade(aStreamTrade);
THEOLIZER_PROCESS(aSerializerTrade, aList);
aSerializerTrade.clearTracking();
// 合成結果検証
auto it = aList.begin();
CorporateCustomer* aCorporateCustomer=dynamic_cast<CorporateCustomer*>(it->get());
THEOLIZER_EQUAL(aCorporateCustomer->mName, "Theoride Technology");
THEOLIZER_EQUAL(aCorporateCustomer->mCapitalStock, 2000000);
THEOLIZER_EQUAL(aCorporateCustomer->mTradeAccounts, 500000);
++it;
IndividualCustomer* aIndividualCustomer=dynamic_cast<IndividualCustomer*>(it->get());
THEOLIZER_EQUAL(aIndividualCustomer->mName, "Yossi Tahara");
THEOLIZER_EQUAL(aIndividualCustomer->mBirthday, "1962/01/01");
THEOLIZER_EQUAL(aIndividualCustomer->mPoint, 12000);
// 結果表示
theolizer::JsonOSerializer<> aSerializer(std::cout);
THEOLIZER_PROCESS(aSerializer, aList);
aSerializer.clearTracking();
}

THEOLIZER_PROCESS(aSerializerMaster, aList);にてマスター・ファイルから回復してます。
THEOLIZER_PROCESS(aSerializerTrade, aList);にて取引ファイルから同じインスタンスへ回復してます。
これにより分割して保存したクラスを合成して回復します。
以上により回復したデータは次のようになります。

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
[
2,
[
[1,"CorporateCustomer",{
"(BaseCustomer)":{
"mName":"Theoride Technology"
},
"mCapitalStock":2000000,
"mTradeAccounts":500000
}]
],
[
[2,"IndividualCustomer",{
"(BaseCustomer)":{
"mName":"Yossi Tahara"
},
"mBirthday":"1962\/01\/01",
"mPoint":12000
}]
]
]

回復結果のまとめ:

クラスメンバ変数内容
CorporateCustomermName"Theoride Technology"
mCapitalStock2,000,000
mTradeAccounts500,000
IndividualCustomermName"Yossi Tahara"
mBirthday1962/01/01
mPoint12,000


1-4-3.間違い易い指定について

クラス・インスタンスをメンバ変数として持つクラスについて、注意点があります。他のクラスを含むクラスを親クラス、他のクラスに含まれるクラスを子クラスとします。
この時、親クラス側で子クラス型のメンバ変数に保存先をAと指定し、子クラスのメンバ変数xに保存先をBとしていた場合、親クラスをBへ保存した時にxが保存されません。これは仕様ですがミスし易い部分です。

以下具体例で説明します。
親クラスをDestinationParent、子クラスをDestinationChildとします。
親クラスはDestinationChild型のmDestinationChildメンバ変数を持っていて、これは保存先としてDestAを指定します。子クラスは2つのメンバ変数mAnnotateAとmAnnotateBを持ち、それぞれ保存先としてDestA、DestBを指定します。

保存先の定義 (source/reference_and_test/basic/common.h)

(
Trade,
DestA,
DestB,
DestC
);

クラス例 (source/reference_and_test/basic2/test_destinations.h)

// ---<<< 子クラス >>>---
struct DestinationChild
{
int mAnnotateA THEOLIZER_ANNOTATE(FS:<theolizerD::DestA>);
int mAnnotateB THEOLIZER_ANNOTATE(FS:<theolizerD::DestB>);
DestinationChild(int iValueA, int iValueB) :
mAnnotateA(iValueA),
mAnnotateB(iValueB)
{ }
};
// ---<<< 親クラス >>>---
struct DestinationParent
{
int mAnnotateA THEOLIZER_ANNOTATE(FS:<theolizerD::DestA>);
int mAnnotateB THEOLIZER_ANNOTATE(FS:<theolizerD::DestB>);
DestinationChild mDestinationChild THEOLIZER_ANNOTATE(FS:<theolizerD::DestA>);
DestinationParent(int iValue=0) :
mAnnotateA(1*iValue),
mAnnotateB(2*iValue),
mDestinationChild(3*iValue, 4*iValue)
{ }
};

保存ソース:(source/reference_and_test/basic2/test_destinations.cpp)

これにて次のように保存されます。

tutorise_destinationsA.json:

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
{
"mAnnotateA":100,
"mDestinationChild":{
"mAnnotateA":300
}
}

mDestinationChildのmAnnotateBはDestBのみ保存なので、tutorise_destinationsA.jsonには保存されません。

tutorise_destinationsB.json:

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
{
"mAnnotateB":200
}

tutorise_destinationsB.jsonには、指示通りmDestinationChildが含まれません。 上記のどちらにもmDestinationChildのmAnnotateBが保存されないことに注意して下さい。

回復ソース:(source/reference_and_test/basic2/test_destinations.cpp)

{
DestinationParent aDestinationParent;
// DestA回復
std::ifstream aStreamA("tutorise_destinationsA.json");
THEOLIZER_PROCESS(aSerializerA, aDestinationParent);
// DestBを合成回復
std::ifstream aStreamB("tutorise_destinationsB.json");
THEOLIZER_PROCESS(aSerializerB, aDestinationParent);
// 合成結果検証
THEOLIZER_EQUAL(aDestinationParent.mAnnotateA, 100);
THEOLIZER_EQUAL(aDestinationParent.mAnnotateB, 200);
THEOLIZER_EQUAL(aDestinationParent.mDestinationChild.mAnnotateA, 300);
THEOLIZER_EQUAL(aDestinationParent.mDestinationChild.mAnnotateB, 0);
// 結果表示
theolizer::JsonOSerializer<> aSerializer(std::cout);
THEOLIZER_PROCESS(aSerializer, aDestinationParent);
}

以上により回復したデータは次のようになります。mDestinationChild.mAnnotateBが0のままです。 これはミスを誘発し易いですので、子クラスに保存先を指定する際は慎重に行って下さい。
なお、基底クラスには親クラス側に保存先を指定できていため、同様のミスは発生しません。

{
"SerialzierName":"JsonTheolizer",
"GlobalVersionNo":1,
"TypeInfoList":[1]
}
{
"mAnnotateA":100,
"mAnnotateB":200,
"mDestinationChild":{
"mAnnotateA":300,
"mAnnotateB":0
}
}


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

2-1.保存の有無指定のテスト

これは使い方説明で用いたDefaultSaveとDefaultNonSaveの保存/回復を、全てのシリアライザ、全ての書式でテストしています。

source/reference_and_test/basic2/test_destinations.cppでテスト関数を定義してます。

  1. 保存処理
    template<class tSerializer>
    void saveSpecifySaving(tSerializer& iSerializer)の前2つ

  2. 回復処理
    template<class tSerializer>
    void loadSpecifySaving(tSerializer& iSerializer)の前2つ


2-2.保存先指定のテスト

source/reference_and_test/basic2/test_destinations.hでテスト用のクラスを定義してます。

DestinationTestChildクラスとDestinationTestParentクラスを用います。両方ともデフォルト保存型です。
前者はint型、後者はDestinationTestChild型のメンバ変数を下記のように持ちます。

メンバ変数保存先指定
mNoAnnotate保存先指定無し
mAnnotateADestAへ保存
mAnnotateBDestBへ保存
mAnnotateABDestAとDestBの両方へ保存
mAnnotateCDestCへ保存
mAnnotateN非保存指定

source/reference_and_test/basic2/test_destinations.cppでテスト関数を定義してます。
下記組み合わせでテストしています。

シリアライザテストしている関数
保存先指定無しsaveSpecifySaving()とloadSpecifySaving()内の最後の1つ
DestAのみsaveDestinations()の先頭で保存し、loadDestinations()で単独回復
DestBのみsaveDestinations()の2番目で保存し、loadDestinations()で合成回復
DestAとDestBsaveDestinations()とloadDestinations()の最後の1つ

以上により、下記組み合わせのテストを行っています。

シリアライザメンバ変数保存先指定されたメンバ変数内のメンバ変数
指定無し指定無し指定無し(mNoAnnotate)
単独指定有り親と同じ指定有り(mAnnotateA, mAnnotateB)
親と一部同じ複数指定有り(mAnnotateAB)
親と異なる指定有り(mAnnotateC)
保存無し指定(mAnnotateN)
複数指定有り上記と同じセット
保存無し指定上記と同じセット
単独指定有り(DestA)上記と同じセット上記と同じセット
単独指定有りで合成回復(DestB)上記と同じセット上記と同じセット
複数指定(DestA,DestB)上記と同じセット上記と同じセット