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

こんにちは。田原です。

データ構造が大きく複雑になってくると全てを同じファイルへ保存するのではなく、保存先を分ける必要が出てくると思います。
例えば、ConfigureファイルやSettingsファイル、Dataファイル等ですね。
また、保存したくない変数もあるでしょう。ファイル・ハンドルとか、一時的なワークエリアなど。
今回はそのような要求に対応できる機能を説明します。

1.保存先指定の使い方

SettingsファイルとDataファイルに分けて保存/回復すること、および、保存しない変数を指定することは下記手順で行います。

  1. 「保存先」を示すシンボルを定義します。
  2. クラスや構造体の各メンバ変数に対してそのシンボルを指定します。
  3. JsonOSerializerなどのシリアライザを使う時に保存先シンボルを指定します。

保存先シンボルをシリアライザへ指定すると、そのシンボルが指定されたメンバ変数が保存/回復されるようになります。
保存先シンボルを指定しない場合は「全て」の意味となり、常に保存/回復されます。
また、「保存しない」という指定もできます。

1-1.「保存先」を示すシンボルを定義します。

まず、全ての保存先指定を行うクラスより前で下記のようなTHEOLIZER_DESTINATIONS()マクロで保存先シンボルを定義します。

THEOLIZER_DESTINATIONS
(
    All,                // 引き継ぎ元保存先定義の最後のシンボル
    Settings,           // 設定情報
    Data                // データ
);

Allは変更不可です。Settings以降を設定できます。

1-2.クラスや構造体の各メンバ変数に対してそのシンボルを指定します。

次に、メンバ変数に保存先を指定するのですが、書式が結構長いのでマクロで短縮定義しましょう。

#define SETTINGS    THEOLIZER_ANNOTATE(FS:<theolizerD::Settings>)
#define DATA        THEOLIZER_ANNOTATE(FS:<theolizerD::Data>)
#define NO_SAVE     THEOLIZER_ANNOTATE(FN)

FSは保存するフィールドでオプションとして保存先シンボルを指定できます。
FNは保存しないフィールドです。(保存しないのですから、保存先は指定できません。)
theolizerD::は実装上の都合によるオマジナイです。

今回はデータ構造自体は前回と同じですので、それに保存先を指定してみます。
科目(Item)は残高をDataファイルへ保存、計算領域のmAssetsIncreaseは保存しない、それ以外のメンバを全てSettingsファイルへ保存するように指定しました。子科目はSettings, Dataファイルの両方へ保存することになるので無指定です。

struct Item
{
    Item*                           mParent         SETTINGS;   // 親科目
    std::string                     mName           SETTINGS;   // 科目名
    bool                            mIsAssets       SETTINGS;   // 資産(財布や預金)
    int                             mAssetsIncrease NO_SAVE;    // 増加した資産金額
    bool                            mDoManage       SETTINGS;   // 残高管理の有無
    int                             mAmount         DATA;       // 残高
    theolizer::ListPointee<Item>    mChildren;                  // 子科目
};

そして、取引(Trade)リストは全てDataファイルへ保存するよう指定しました。

struct HouseholdAccounts
{
    Item                mItemTree  POINTEE; // 科目ツリー
    std::list<Trade>    mTradeList DATA;    // 取引のリスト
};

1-3.JsonOSerializerなどのシリアライザを使う時に保存先シンボルを指定します。

下記のように指定して保存/回復します。

保存

        // 保存処理(Settings)
        {
            // 保存先のファイルをオープンする
            std::ofstream   aStream(aSettings);

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

            // 家計簿をSettings.jsonファイルへ保存する
            THEOLIZER_PROCESS(js, aHouseholdAccountsSave);

            // オブジェクト追跡の締め
            js.clearTracking();
        }

        // 保存処理(Data)
        {
            // 保存先のファイルをオープンする
            std::ofstream   aStream(aData);

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

            // 家計簿をData.jsonファイルへ保存する
            THEOLIZER_PROCESS(js, aHouseholdAccountsSave);

            // オブジェクト追跡の締め
            js.clearTracking();
        }

回復

    // 回復処理(Settings)
    {
        // 回復元のファイルをオープンする
        std::ifstream   aStream(aSettings);

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

        // 家計簿をSettings.jsonファイルから回復する
        THEOLIZER_PROCESS(js, aHouseholdAccountsLoad);

        // オブジェクト追跡の締め
        js.clearTracking();
    }

    // 回復処理(Data)
    {
        // 回復元のファイルをオープンする
        std::ifstream   aStream(aData);

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

        // 家計簿をData.jsonファイルから回復する
        THEOLIZER_PROCESS(js, aHouseholdAccountsLoad);

        // オブジェクト追跡の締め
        js.clearTracking();
    }

2.今回のソース・コード

前回のソース・コードに対して主に上記変更を行っただけですので、まとめてソースを示します。
保存先を指定しただけで家計簿のメイン処理に影響するような変更をしていませんので、sub.cppはコメント以外何も修正していません。

なお、次回解説するバージョン・アップ/ダウン処理用に2行追加しています。

main.cpp : #define THEOLIZER_GLOBAL_VERSION_TABLE
common.h : THEOLIZER_DEFINE_GLOBAL_VERSION_TABLE(GlobalVersionTable, 1);

これらについては次回説明しますので、今は気にしないで下さい。

2-1.構造体定義

//############################################################################
//      Theolizer解説用サンプル・プログラム3
//
//          簡単な家計簿用共通定義
//############################################################################

#if !defined(COMMON_H)
#define COMMON_H

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

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

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

// ***************************************************************************
//      保存先定義
// ***************************************************************************

THEOLIZER_DESTINATIONS
(
    All,                // 引き継ぎ元保存先定義の最後のシンボル
    Settings,           // 設定情報
    Data                // データ
);

#define SETTINGS    THEOLIZER_ANNOTATE(FS:<theolizerD::Settings>)
#define DATA        THEOLIZER_ANNOTATE(FS:<theolizerD::Data>)
#define NO_SAVE     THEOLIZER_ANNOTATE(FN)

// ***************************************************************************
//      グローバル・バージョン番号テーブル
// ***************************************************************************

THEOLIZER_DEFINE_GLOBAL_VERSION_TABLE(GlobalVersionTable, 1);

// ***************************************************************************
//      構造体定義
// ***************************************************************************

#define POINTEE     THEOLIZER_ANNOTATE(FS:<>Pointee)

//----------------------------------------------------------------------------
//      科目管理
//----------------------------------------------------------------------------

struct Item
{
    Item*                           mParent         SETTINGS;   // 親科目
    std::string                     mName           SETTINGS;   // 科目名
    bool                            mIsAssets       SETTINGS;   // 資産(財布や預金)
    int                             mAssetsIncrease NO_SAVE;    // 増加した資産金額
    bool                            mDoManage       SETTINGS;   // 残高管理の有無
    int                             mAmount         DATA;       // 残高
    theolizer::ListPointee<Item>    mChildren;                  // 子科目
};

typedef theolizer::ListPointee<Item>::iterator  ChildIterator;

//----------------------------------------------------------------------------
//      取引記録
//----------------------------------------------------------------------------

struct Date
{
    short       mYear;                  // 年
    char        mMonth;                 // 月(1-12)
    char        mDay;                   // 日(1-31)
};

struct Trade
{
    Date        mDate;                  // 取引日
    Item*       mDebitItem;             // 購入品(借方科目)
    int         mAmount;                // 金額
    Item*       mCreditItem;            // 財布 (貸方科目)
    std::string mNote;                  // 備考
};

//----------------------------------------------------------------------------
//      家計簿
//----------------------------------------------------------------------------

struct HouseholdAccounts
{
    Item                mItemTree  POINTEE; // 科目ツリー
    std::list<Trade>    mTradeList DATA;    // 取引のリスト
};

typedef std::list<Trade>::iterator  TradeIterator;

// ***************************************************************************
//      関数群
// ***************************************************************************

//----------------------------------------------------------------------------
//      初期設定
//----------------------------------------------------------------------------

void initialize(HouseholdAccounts* oHouseholdAccounts);
void display(   HouseholdAccounts* iHouseholdAccounts);
void calculate( HouseholdAccounts* ioHouseholdAccounts);

#endif

2-2.データの保存と回復、および、簡単な集計と表示

//############################################################################
//      Theolizer解説用サンプル・プログラム3
//
//          メイン処理(データ保存/回復サンプル)
//############################################################################

#define THEOLIZER_GLOBAL_VERSION_TABLE

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

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

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

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

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

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

    std::string aSufix="";
    if (1 < argc)
    {
        aSufix=argv[1];
    }
    std::cout << theolizer::print("Sufix=%s\n", aSufix);

    std::string aSettings = theolizer::print("Settings%s.json", aSufix).str();
    std::string aData     = theolizer::print("Data%s.json", aSufix).str();

//----------------------------------------------------------------------------
//      家計簿設定
//----------------------------------------------------------------------------

    // サフィックス指定されたら回復処理のみ実行する
    if (aSufix == "")
    {
        // 保存用家計簿
        HouseholdAccounts   aHouseholdAccountsSave;

        // 初期設定(科目と取引データを設定している)
        initialize(&aHouseholdAccountsSave);

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

        std::cout << u8"<<< 保存するサンプル・データ >>>\n";
        display(&aHouseholdAccountsSave);

        // 保存処理(Settings)
        {
            // 保存先のファイルをオープンする
            std::ofstream   aStream(aSettings);

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

            // 家計簿をSettings.jsonファイルへ保存する
            THEOLIZER_PROCESS(js, aHouseholdAccountsSave);

            // オブジェクト追跡の締め
            js.clearTracking();
        }

        // 保存処理(Data)
        {
            // 保存先のファイルをオープンする
            std::ofstream   aStream(aData);

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

            // 家計簿をData.jsonファイルへ保存する
            THEOLIZER_PROCESS(js, aHouseholdAccountsSave);

            // オブジェクト追跡の締め
            js.clearTracking();
        }
    }

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

    // 回復用家計簿(正確に回復できていることを示すため、保存用と別領域)
    HouseholdAccounts   aHouseholdAccountsLoad;

    // 回復処理(Settings)
    {
        // 回復元のファイルをオープンする
        std::ifstream   aStream(aSettings);

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

        // 家計簿をSettings.jsonファイルから回復する
        THEOLIZER_PROCESS(js, aHouseholdAccountsLoad);

        // オブジェクト追跡の締め
        js.clearTracking();
    }

    // 回復処理(Data)
    {
        // 回復元のファイルをオープンする
        std::ifstream   aStream(aData);

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

        // 家計簿をData.jsonファイルから回復する
        THEOLIZER_PROCESS(js, aHouseholdAccountsLoad);

        // オブジェクト追跡の締め
        js.clearTracking();
    }

//----------------------------------------------------------------------------
//      内容表示と簡単な集計処理
//----------------------------------------------------------------------------

    std::cout << u8"\n\n<<< 回復したサンプル・データ >>>\n";
    display(&aHouseholdAccountsLoad);

    std::cout << u8"\n\n<<< サンプルの集計結果 >>>\n";
    calculate(&aHouseholdAccountsLoad);

    return 0;
}

2-3.家計簿アプリケーションのメインに当たる部分

sub.cpp

2-4.自動生成されたソース

main.cpp.theolizer.hpp

3.実行結果

3-1.標準出力

result.log

2-5.CMakeLists.txt

CMakeLists.txt

3-2.Theolzierにて保存された設定ファイル

Settings.json

3-3.Theolzierにて保存されたデータファイル

Data.json

4.解説

科目(Item)は「葉」に当たる構造体のメンバ、および、取引(Trade)リストはに保存先を指定しました。
「葉」より上位の構造体(mItemTree)には保存先を指定していません。ということは「全て」に保存します。
従って、科目(Item)は、指定に従って分割されSettingsファイルとDataファイルの両方へ保存されます。
逆にmTradeListは大元で保存先を指定し、下位の部分では指定していません。大元でDataファイル指定しているため、Settingsファイルへ保存に行きません。そのため、下位の部分で「全て」になっていてもSettingsファイルへは保存されません。

5.まとめ

保存先指定は意外に便利に使えます。まずは保存/回復先のファイルを指定するために使いましたが、例えば取引(Trade)を他のPCから送って貰って処理するような使い方もできます。

次回はバージョン・アップ/ダウン機能を紹介します。乞うご期待。