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

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

デフォルト値について

最初にデフォルト値について説明します。
enumのシンボルを定義している先頭のシンボルの値をTheolizerは「デフォルト値」として用います。
THEOLIZER_ANNOTATE(ES)による指定を間違った場合、enum値保存/回復やバージョン・アップ/ダウン時に対応するシンボルが見つからない場合があります。その時、警告を報告(例外にはなりません。)し、変換先の値を「デフォルト値」にします。


1.バージョン番号を変えないでenum型を修正

バージョン番号を変えない場合に可能なenum型の修正は以下のものがあります。

  1. シンボル名保存の場合
    • シンボル値の変更
      シリライズ・データ中にシンボル値は記録されないため、シリアライズ処理とは無関係な変更です。
      従って、特にシリアライズ用に指定する必要はありません。
    • シンボル名の変更
      変更前のシンボル名で保存されたシリアライズ・データを回復できるようにするためには、変更前のシンボル名がどのシンボルへ対応するのか、Theolizerへ教える必要があります。
      そのためにTHEOLIZER_ANNOTATE(ES:)の第1パラメータを使います。

  2. シンボル値保存の場合
    • シンボル名の変更
      シリライズ・データ中にシンボル名は記録されないため、シリアライズ処理とは無関係な変更です。
      従って、特にシリアライズ用に指定する必要はありません。
    • シンボル値の変更
      変更前のシンボル値で保存されたシリアライズ・データを回復できるようにするためには、変更前のシンボル値がどのシンボルへ対応するのか、Theolizerへ教える必要があります。
      そのためにTHEOLIZER_ANNOTATE(ES:)の第2パラメータを使います。

  3. 共通
    • シンボルの追加
      古いシリアライズ・データに、新規追加したシンボル名/値が記録されることはありませんので、シリアライズ用の対応は何もありません。
    • シンボルの削除
      削除前のシンボル名/値に対応するシンボルをTHEOLIZER_ANNOTATE(ES:)で指定していない場合、削除されたシンボル名/値を回復する時に警告が報告されます。それを避けるためには、例えば「デフォルト値」へシンボル名/値が変更されたように指定して下さい。複数の旧シンボルを1つの現シンボルへ指定可能ですので複数のシンボルを削除した場合にも対応できます。

注:シンボル値保存/シンボル名保存の指定方法についてはenum型のバリエーション を参照下さい。

THEOLIZER_ANNOTATE(ES:)の指定方法は以下の通りです。

enumシンボル THEOLIZER_ANNOTATE(ES:シンボル名リスト:シンボル値リスト)=値,
:

「シンボル名リスト」はシンボル名保存時に指定します。
「シンボル値リスト」はシンボル値保存時に指定します。 「=値」は通常のenum型と同じく省略可能です。

THEOLIZER_ANNOTATE(ES:)を記述する位置が直感的な位置ではないですので、ご注意下さい。
シンボルの直後です。この機能の実装にclangの属性指定(attribute())機能を使っていますが、enum型のシンボルの属性指定は、この位置のみ書けます。


1-1.シンボル名保存の変更サンプル

下記の修正を行うサンプルです。

  • eesnName11をeesnName10へ変更
  • eesnName20, eesnName21, eesnName22をeesnName20へ統合
    「eesnName21, eesnName22を削除して、eesnName20へ割り当てた」のと同じです。

変更前(ver1a)のソース(source/reference_and_test/ver1a/test_modify_enum.h)

enum EnumSymName
{
eesnName11, // 単にシンボル変更
eesnName20, // 3つのシンボルを先頭の1つへ統合する
eesnName21,
eesnName22,
(後略)

変更後(ver1b)のソース(source/reference_and_test/ver1b/test_modify_enum.h)

enum EnumSymName
{
eesnName10 THEOLIZER_ANNOTATE(ES:eesnName11),
// 3つのシンボルを先頭の1つへ統合する
eesnName20 THEOLIZER_ANNOTATE(ES:eesnName21,eesnName22),
(後略)


1-2.シンボル値保存の変更サンプル

修正内容はEnumSymNameの場合と同等です。

変更前(ver1a)のソース(source/reference_and_test/ver1a/test_modify_enum.h)

enum EnumSymVal
{
eesvValue11=11, // 単にシンボル変更
eesvValue20=20, // 3つのシンボルを先頭の1つへ統合する
eesvValue21=21,
eesvValue22=22,
(後略)

変更後(ver1b)のソース(source/reference_and_test/ver1b/test_modify_enum.h)

enum EnumSymVal
{
eesvValue10 THEOLIZER_ANNOTATE(ES::11)=10,
// 3つのシンボルを先頭の1つへ統合する
eesvValue20 THEOLIZER_ANNOTATE(ES::21,22)=20,
(後略)


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

enum型解説の途中ですが、ここでバージョン・アップ前の準備について説明します。
今までバージョン管理していなかった時にバージョン管理を始める時、3-2.バージョン番号管理について で説明した「グローバル・バージョン番号テーブル」の生成が必要になります。

そのために必要な作業は以下の通りです。

  • グローバル・バージョン番号テーブルの宣言
  • グローバル・バージョン番号テーブル実体定義


2-1.グローバル・バージョン番号テーブルの宣言

シリライズ処理するコンパイル単位全てがインクルードするヘッダファイルで宣言して下さい。
具体的には下記書式で宣言します。

THEOLIZER_DEFINE_GLOBAL_VERSION_TABLE(テーブル名, グローバル・バージョン番号);

テーブル名は任意の識別子です。これはtheolizer::internal::global_table名前空間内で必要なコードを定義する時の識別子の一部として用いられます。
グローバル・バージョン番号は最初は必ず1として下さい。その後、バージョンを上げる度に1づつインクリメントして下さい。

グローバル・バージョン番号が1つ増える度にクラスやenum型のローカル・バージョン番号も最大1つ上げることができます。既に現在のグローバル・バージョン番号でクラスかenum型のローカル・バージョンを上げており、それを更に上げる場合はグローバル・バージョン番号も上げる必要があります。
グローバル・バージョン番号が決まればクラスやenum型のローカル・バージョン番号も決まるようにする必要があるためです。

サンプルをver1cに用意してます。(source/reference_and_test/ver1c/common.h)


2-2.グローバル・バージョン番号テーブル実体定義

グローバル・バージョン番号テーブル実体は通常のグローバル変数(配列)として定義されますので、どれか1つのコンパイル単位でのみ実体定義します。

#define THEOLIZER_GLOBAL_VERSION_TABLE

そのコンパイル単位のソース・ファイル(.cpp)の先頭付近で下記を定義することで、Theolizerドライバがこのマクロ定義を認識し、実体をその.cppファイル用の*.theolizer.hpp内に自動生成します。

また、そのコンパイル単位をTheolizerドライバが解析する時に、シリアライズする全てのクラスとenum型の定義とそのシリアライズ指定が必要になります。そこで、当該コンパイル単位ではシリアライズする全ての型を定義したヘッダ・ファイルをインクルードして下さい。

サンプルをver1cに用意してます。(source/reference_and_test/ver1c/main.cpp)

// theolizerライブラリ(グローバル・バージョン番号テーブル生成のためにも必要)
#include <theolizer/serializer_binary.h>
#include <theolizer/serializer_fast.h>
// プロジェクト・ヘッダ
#include "common.h"
// グローバル・バージョン番号テーブル生成
#define THEOLIZER_GLOBAL_VERSION_TABLE
#include "test_modify_enum.h"
#include "test_modify_class.h"
#include "test_modify_complex.h"
// 自動生成ソース
#include "main.cpp.theolizer.hpp"


3.バージョン番号を変えてenum型を修正

バージョン番号を更新して、enum型をバージョン・アップすることで次の変更ができます。

  • 過去割り当てていたシンボルを別の意味で再割当て
    バージョン番号を変えない場合、古いプログラムで保存したシンボルを他のシンボルへ割り当てるため、古いシンボルも割り当てが存在するため変更できません。
    バージョン番号を更新することで、新しいバージョンでは古いシンボルは未定義なので、それを新たに割り当てしても混乱を生じません。

  • シンボル名保存とシンボル値保存の切り替え
    ローカル・バージョン番号を1つ上げて、シリアライズ指定マクロ名を変えるだけです。

3-1.バージョン・アップ手順

enum型をバージョン・アップする時の手順は次の通りです。

  1. まだ作っていないなら、グローバル・バージョン番号テーブルを作成しておきます。

  2. 対象enum型が非侵入型完全自動ならば非侵入型半自動へ変更します。
    完全自動のバージョン・アップに対応していません。

  3. ビルドし、Theolizerドライバにて自動生成ソースへ反映します。

  4. 必要な場合はグローバル・バージョン番号をインクリメントします。
    2-1.グローバル・バージョン番号テーブルの宣言 参照。

  5. 対象enum型のローカル・バージョン番号をインクリメントします。
    2.非侵入型半自動enum型 参照。
    この時、バージョン・アップ前のバージョンで付けていたTHEOLIZER_ANNOTATE()を一旦全て削除します。
    前バージョンで付けていたものは全て前バージョンへ指定ですので、次のバージョンでは指定内容が完全に異なります。

  6. 対象enum型の定義を変更し、前バージョンとの対応付けを行います。
    具体的には後述します。

  7. ビルドします。


3-2.非侵入型半自動への変更方法

単純にTHEOLIZER_ENUM()マクロで指定しするだけです。対象のenum型定義よりも後、*.theolizer.hppをインクルードする前にTHEOLIZER_ENUM()マクロを置いて下さい。なお、THEOLIZER_ENUM_VALUE()でシンボル値保存へ直接変更することはできません。シンボル値保存へ変更する場合は、ローカル・バージョン番号を更新する時に行って下さい。

サンプル
test_modify_enum.hで定義しているEnumFullAutoとScopedEnumFullAutoはver1a, ver1bまでは非侵入型完全自動ですが、ver1cにて非侵入型半自動へ修正しています。

半自動型へ変更した(ver1c)のソース(source/reference_and_test/ver1c/test_modify_enum.h)

enum EnumFullAuto
{
// 中略
};
THEOLIZER_ENUM(EnumFullAuto, 1); // バージョン・アップに備え半自動型へ変更
enum class ScopedEnumFullAuto : long
{
// 中略
};
THEOLIZER_ENUM(ScopedEnumFullAuto, 1); // バージョン・アップに備え半自動型へ変更


3-3.バージョン・アップ時のシンボル対応

enum型はバージョン・アップする時、前バージョンと現バージョン間のシンボル値の対応を指定することができます。現バージョンのシンボルに対して、対応する前バージョンのシンボル値を指定します。

この場合、THEOLIZER_ANNOTATE(ES:)の第3パラメータで指定します。

enumシンボル THEOLIZER_ANNOTATE(ES:::対応する前バージョンのシンボル値)=値,
:

「対応する前バージョンのシンボル値」はenumシンボルではなく直接数値で指定して下さい。前バージョンのenum型そのものは存在しません。しかし、旧版のenum型シンホルの値を変更することも有りえませんので指定可能です。
また、前バージョンとシンボル値が変化していない場合は「対応する前バージョンのシンボル値」の指定を省略できます。

3-3-1.内部処理概要

  • バージョン・アップ処理
    前バージョンのシンボル値に対して、次のバージョンの各シンボルを先頭から順に枚挙し、「対応する前バージョンのシンボル値」が一致するものを探します。最初に一致したシンボルへ変換します。
    複数のシンボルが一致する時は、先に定義されたシンポルへ変換されます。
    なお、一致するものが見つからなかった時は、デフォルト値を設定し、警告を報告します。

  • バージョン・ダウン処理
    前バージョンの各シンボルを先頭から順に枚挙し、現バージョンのシンボルの「対応する前バージョンのシンボル値」と一致するものを探します。最初に一致したシンボルへ変換します。
    複数のシンボルが一致する時は、先に定義されたシンポルへ変換されます。
    なお、一致するものが見つからなかった時は、デフォルト値を設定し、警告を報告します。

3-3-2.以前使用したシンボルの再割当て

これは、シンボルを名前変更したり削除(デフォルト値へ割り当て)したりした後、変更前に使っていたシンボルを他の意味で使いたいケースを想定しています。
この場合、バージョン・アップすることで、そのシンボルを他のシンボルへの再割当てすることができます。その際、3-3.バージョン・アップ時のシンボル対応 の方法で指定します。

再割当てしたいシンボルを「enumシンボル」に追加します。
この時、「対応する前バージョンのシンボル値」には前バージョンにおける「デフォルト値」を指定して下さい。

なお、上記の指定方法で、バージョン・アップ前のバージョンの特定のシンボルを、以前使っていたシンボルへ戻すことも可能です。ただし、これはシンボルの意味の変更を伴いませんので、バージョン・アップしても問題ないですが、バージョン・アップする必要はありません。

下記の修正を行うサンプルです。

  • 旧バージョンの各種指定を削除する
  • 以前使っていたeesnName11を新しいバージョンのenum型へ新規追加する

バージョン・アップ前(ver1c)のソース(source/reference_and_test/ver1c/test_modify_enum.h)

enum EnumSymName
{
eesnDefault,
eesnValue1=10,
eesnName10 THEOLIZER_ANNOTATE(ES:eesnName11),
eesnName20 THEOLIZER_ANNOTATE(ES:eesnName21,eesnName22),
eesnName41,
eesnName42,
eesnName30 THEOLIZER_ANNOTATE(ES:eesnName31,eesnName32,eesnName33),
eesnName51,
eesnName52,
eesnDeleted THEOLIZER_ANNOTATE(ES:eesnDelete)
};

バージョン・アップ後(ver2a)のソース(source/reference_and_test/ver2a/test_modify_enum.h)

enum EnumSymName
{
// 変更無し
eesnDefault,
eesnValue1=10,
eesnName10,
eesnName20,
eesnName41,
eesnName42,
eesnName30,
eesnName51,
eesnName52,
eesnDeleted,
// 追加(前バージョンのeesnDefaultに対応させる)
eesnName11 THEOLIZER_ANNOTATE(ES:::0)
};


3-4.シンボル名保存とシンボル値保存の切り替え

開発当初は変更対応しやすいシンボル名保存が使い勝手が良いです。(C#での経験ですが、シンボル値を気にしないで良いのはたいへんありがたかったです。)
ある程度枯れてきたら、シンボル名では効率が悪いのでシンボル値へ変更したくなります。
そして、再度、大改造する際に一旦シンボル名へ戻したい時もあるでしょう。
このようなシナリオに対応することを想定しています。

変更は3-1.バージョン・アップ手順 の手順に従います。この6.の手順にて下記マクロ名を切り替えます。

  • THEOLIZER_ENUM
  • THEOLIZER_ENUM_VALUE

下記の修正を行うサンプルです。

  • 旧バージョンのシンボル名保存enum型をシンボル値保存enum型へ変更(EnumSymName)

バージョン・アップ前(ver2a)のソース(source/reference_and_test/ver2a/test_modify_enum.h)

THEOLIZER_ENUM(EnumSymName, 2);

バージョン・アップ後(ver3a)のソース(source/reference_and_test/ver3a/test_modify_enum.h)

THEOLIZER_ENUM_VALUE(EnumSymName, 3);

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

enum型の修正に関する網羅的なテストは 4.テスト・プログラムの構造 で説明した各フォルダの下記2つのファイルにて実装しています。

  • test_modify_enum.h
  • test_modify_enum.cpp

unscoped enum型とscoped enum型について下記の修正を行い、4-1-2.テストの組み合わせ の各バージョン間で正しく回復できることを確認しています。

バージョン修正点特記事項
ver1a→ver1b1.シンボル値変更
2.シンボル名変更(a→b)
3.シンボル名変更(a,b,c→a)
4.シンボル名変更(a,b,c→d)
5.シンボル名削除(指定ミス)

単純変更
3つから先頭の1つへ対応
3つから別の1つへ対応
指定ミスは対応先指定漏れ警告が出ることを確認
ver1b→ver1c1.グローバル・バージョン番号テーブル作成
2.完全自動型を半自動型へ変更
3.シンボル名削除指定ミスの修正
警告が消えることを確認
ver1c→ver2a1.バージョン・アップ
2.以前使っていたシンボルの再割当て
ver2a→ver3a1.バージョン名保存→バージョン値保存
2.バージョン値保存→バージョン名保存
3.前バージョンとの対応指定ミス
バージョン・アップ処理と回復処理で警告が出ることを確認
(元完全自動型はバージョン・アップ無し)
ver3a→ver3b指定ミスの修正警告が消えることを確認
(元完全自動型はバージョン・アップ無し)

ver3a/test_modify_enum.cppのtutoriseModifyEnum()関数にて対応シンボルが無い時のバージョン・ダウン処理、および、保存処理に於ける警告検出をjson形式を用いてテストしています。

ver3b/test_modify_enum.cppのtutoriseModifyEnum()関数にて上記警告が消えることをテストしています。