目次

  1. シリアライズ処理の仕組み
  2. シリアライザ(serializer)
  3. デシリアライザ(deserializer)
  4. 付録

道具箱 - シリアライザ

  • シリアライズ処理の仕組み
  • シリアライズ処理は、構造を持ったデータを、ストリームのような1次元のデータに変更する処理で、 逆処理のデシリアライズとともに、データの保存や通信になくてはならないものです。
    C++標準ライブラリの出力/入力ストリームには、 基本型やstringなどごく限られたデータのシリアライズ/デシリアライズがサポートされますが、 実用には十分なものではありません。また、これをそのまま拡張しても、値指向スタイルを想定したものでは、 ポインターの処理などが困難になります。
    そこで、道具箱(Open Middleware Toolkit)では、 ポインターの指し先のデータを含めたシリアライズ/デシリアライズが可能な ユーティリティ・ライブラリ(以下、シリアライザと呼びます)を用意します。

    このシリアライザは、以下のような特長をもっています。

    開発者は、シリアライザを使用してシリアライズ/デシリアライズ処理を記述することで、 アプリケーション・プログラムで使用するデータを保存・通信するためのフォーマットを その都度設計・実装する必要から開放されます。

    以下では、簡単な使用例を観たあとで、

    の順に解説を行い、シリアライザの構成と利用方法を概観します。

    シリアライズ処理の概要

    まず、簡単なコードでシリアライズ処理の概要を観てみましょう。

    	#include <cstdio>
    	#include <string>
    	#include <omt/common.h>
    	#include <omt/plist.h>
    	#include <omt/srz.h>
    
    	using namespace omt;
    
    	class my_class
    	{
    		typedef phash<char*, store_policy<char*> > attrs;
    
    		int		m_id;
    		std::string	m_name;
    		attrs		m_attr;
    		
    	    public:
    		my_class() : m_id( 0 ), m_name( 0 ) { }		// <--(5)
    		my_class( int id, const char* name )
    			: m_id( id ), m_name( name ) { }
    		~my_class() { }
    
    		// ... some member functions here ...
    
    		void serialize( serializer& sz )		// <--(1)
    		{
    			sz << begin
    			    << m_id << m_name << m_attr
    			<< end;					// <--(2)
    		}
    		void deserialize( deserializer& ds )		// <--(3)
    		{
    			ds >> begin
    			    >> m_id >> m_name >> m_attr
    			>> end;					// <--(4)
    		}
    	
    	    private:
    		my_class( const my_class& );
    		my_class& operator=( const my_class& );
    	};
    
    	class my_pool
    	{
    		typedef plist<my_class*, store_policy<my_class*> > pool;
    		pool	my_pool;
    
    	    public:
    		// ...
    
    		void load( FILE* fin );
    		void save( FILE* fout );
    	};
    
    	void my_pool::save( FILE* fout )
    	{
    		serializer	sz( fout );			// <--(6)
    
    		sz << my_pool;
    	}
    	void my_pool::load( FILE* fin )
    	{
    		m_pool.clear();
    
    		deserializer	ds( fin );			// <--(7)
    
    		ds >> my_pool;
    	}
    このコードでは、道具箱コンテナハッシュ・テーブル を使ったユーザー定義クラス(my_class)をシリアライズ処理に対応さぜ、 このクラスを複数プールするクラス(my_pool)に、 ファイルへの保管とロード機能を定義しています。 ユーザー定義クラスをシリアライズ処理に対応させるのに必要なことは、 の3点だけです。
    my_classのメンバーは基本型、C++の文字列、道具箱コンテナなので、 いずれもそのまま使用が可能で、(2)のシリアライズ処理、(4)のデシリアライズ処理とも、 非常にシンプルに記述できます。

    上記の3つの処理を記述すると、<omt/srz.h>ヘッダーにあるテンプレートによって、 自動的に、クラスの変数をシリアライズ/デシリアライズ演算子( << と >> )の右辺値に 置くことができるようになります。
    これは、シリアライズ可能になったクラスは、他のクラスのメンバーになった際にも 基本型などと同様にシリアライズ/デシリアライズできることを意味します。 実際、道具箱コンテナとユーザー定義クラスを組み合わせて作られた、 複雑な構成のデータも処理することが可能です。
    処理中の begin/end は、リストを作る(間の要素を括弧でくくる)ためのマニピュレータで、 記述しなくても動作するコードが作れますが、テストやデバッグで重宝するため、 クラス用のシリアライズ/でシリアライズ処理のコーディング・スタイルとして使用が推奨されます。

    このシリアライズ/デシリアライズ処理に使用されている serializer/deserializer が、 処理の出力/入力ストリームを表しているクラスで、このクラスの変数に対して シリアライズ演算子( << )/デシリアライズ演算子( >> )を使うことで、 処理に対応した変数の内容をストリームに書き出す/から読み込むことが可能です。

    プール(my_pool)に定義した保存/ロードを行う関数では、Cのファイルから serializer/deserializerの 変数を作っています(6)(8)が、 この他にもメモリ上の文字列(道具箱の拡張文字列バッファ)や、 標準C++ライブラリの入出力ストリーム(std::istream/std::ostream)とその派生クラス、 文字列クラス(std::string)なども使うことができます。 メモリ上とファイル上のストリームを区別せずに扱えることで、

    などなど、様々な応用がききます。

    Note:
    道具箱ライブラリは、C++標準ライブラリと併用することも、独立して使用することもできます。
    このコードは、C++標準ライブラリの文字列と、Cの関数を併用して記述していますが、 ポインタの処理で述べるように、char*/const char* を使用して標準C++ライブラリを 使用しないで記述することも可能です。
    このことは、C言語をマスターさえしていれば、 非常に複雑な構成をもつC++標準ライブラリをマスターすることなく、 比較的容易に道具箱ライブラリを使うことができることを意味しています。

    外部ストリーム・フォーマット

    シリアライズ処理の外部出力フォーマット(以下外部ストリームと呼びます)は、 ASCII制御文字を含まない8bit文字列(コードはASCII 7bitコードのみを使用)であるため、 テキスト・ファイルに保管でき、内容確認や編集も可能です。

    構造は比較的単純で、構文要素を並べたものをその構造にあわせて括弧で括ったもので、 Lispのs-expressionに似ています。
    空白文字やタブ、改行、カンマ(,)などはデリミタ(構文要素の区切り)としての役割しかなく、 構文要素間に自由に置くことができます。 そのため、同じ内容のデータであっても、通信・保存用に容量を最小化したものから、 テストケース用にインデントを施して可読性を高くしたものまで、さまざまな表現形式が可能です。
    	// 例
    	("\"hello, world!\"\\n"'enhjdmI='-468 0.123(1 2 3)(0 0 0)1)
    

    外部ストリームの基本となる構成要素は以下の6種類です。

    Note:
    外部ストリームの拡張BNFによる定義は以下のようになります。
    /.../はPOSIX基本正規表現を意味します。

    Serialized Stream Symtax
    	serialized_stream ::= [ version sequence ][ term ]*
    	version 	::= SYM
    	sequence	::= INT		// 0 or negative: error
    	term		::= [ control ]* data
    	control		::= PGM [ INT | SYM ]
    	data		::= list | string | binary | integer | floating | bool | null | pointer
    	list		::= LPR [ term ]* RPR
    	string		::= STR
    	binary		::= BIN
    	integer		::= INT
    	floating	::= FLT
    	bool		::= BOL
    	null		::= NUL
    	pointer	   	::= SYM [ LBL term ]
    
    Token and Character Set ( tokenizer setting ):
    	INT ::= /[-][0-9][0-9]*/
    	      | /0[0-7][0-7]*/
    	      | /0x[0-9A-F][0-9A-F]*/
    	FLT ::= /[-][0-9][0-9]*[.[0-9]*][e[-][0-9][0-9]*]/
    	      | /[-][0-9]*.[0-9][0-9]*[e[-][0-9][0-9]*]/
    	STR ::= '"' escaped_c_string '"'
    	BIN ::= '\'' base64_encoded '\''
    	LPR ::= '('
    	RPR ::= ')'
    	LBL ::= '@'
    	PGM ::= '#'
    	BOL ::= '0' | '1'
    	NUL ::= '0'
    	SYM ::= /[a-zA-Z:_][0-9a-zA-Z.-:_]*/
    versionとsequenceは作成された外部ストリームのIDに相当するものですが、必須ではなく、 アプリケーションごとに使用するかどうかを決めることができます。
    また、boolとnullは実装上必要ない定義(integerに含まれるもの)ですが、 意味を明確にするために敢えて記述しています。

    入出力ストリーム

    外部ストリームを実際に保持し処理する入出力ストリームは、<omt/srz.h>中に定義されるクラスです。

    シリアライズ処理の出力ストリーム(serializer)は、Cのファイル(FILE*)、 道具箱の拡張文字列バッファ(omt::xstr&)、 C++標準ライブラリの出力ストリーム(std::ostream&、std::ofstream&、std::ostringstream& など) および文字列(std::string&)のいずれかを使って、作成することができます。
    また、デシリアライズ処理の入力ストリーム(deserializer)は、 Cのファイル(FILE*)、Cの文字列(const char*)、道具箱の拡張文字列バッファ(omt::xstr&)、 C++標準ライブラリの入力ストリーム(std::istream&、std::ifstream&、std::istringstream& など)、 および文字列(std::string&)のいずれかを使って、作成することができます。

    このことは、シリアライズ処理がファイル、メモリのいずれも使用できることを意味しています。
    たとえば、通信に使用する外部ストリームをデバッグ用にファイルに落として検証したり、ファイル上に 外部ストリーム・データを用意して、通信のテストを行うといったことが、これにより容易に行えます。

    デシリアライズされたポインタが指すデータ領域の管理

    ポインタ指向のプログラミングでシリアライズ処理をする場合、 ポインタの指し先のデータをどう処理するかが問題になります。

    そのため、道具箱コンテナのシリアライザ/デシリアライザは、 ポインタの指し先がシリアライズ可能な型の変数であれば、 ポインタを処理することができるよう設計されています。

    まず、シリアライズ処理の際には、ポインタがその指し先のデータを所有しているか否か (破棄の責任を負うか否か)にかかわらず、ポインタと指し先のデータをシリアライズします。
    これに対してデシリアライズ処理は、読み込んだストリームから作成したデータは デシリアライザが所有者となって管理します。
    したがって、そのデータを指すポインタが指し先を所有していない場合には、 ポインタに対してデシリアライザが管理するデータのアドレスが設定されるだけですが、 ポインタが指し先を所有する場合、シリアライズ処理で、 古いデータの削除と新しいデータの複製処理を行う点に注意が必要です。

    Note:
    デシリアライズ処理は、ポインタへの代入を行うことになるので、 ポインタの指し先を所有している場合は、setterの作成と同じ配慮が必要になるわけです。

    以下、my_class のメンバ m_name をchar*で作成する場合を考えてみましょう。

    	class my_class
    	{
    		typedef phash<char*, store_policy<char*> > attrs;
    
    		int		  m_id;
    		char*		m_name;			// <--(1)
    		attrs		  m_attr;
    		
    	    public:
    		my_class() : m_id( 0 ), m_name( 0 ) { }	
    		my_class( int id, const char* name )
    			: m_id( id ), m_name( dup( name )) { }
    		~my_class() { if ( m_name ) delete [] m_name; }
    
    		// ... some member functions here ...
    
    		void serialize( serializer& sz )
    		{
    			sz << begin
    			    << m_id << m_name << m_attr
    			<< end;	
    		}
    		void deserialize( deserializer& ds )
    		{
    			char*	p;				// <--(2)
    
    			ds >> begin
    			    >> m_id >> p >> m_attr
    			>> end;
    
    			if ( m_name ) delete [] m_name;		// <--(3)
    			m_name = dup( p );			// <--(4)
    		}
    	
    	    private:
    		my_class( const my_class& );
    		my_class& operator=( const my_class& );
    	};
    このケースでは、コンストラクタおよびデストラクタのコードからも解るように、 m_nameが指し先の領域を管理しています。 そのため、デシリアライザからm_nameに直接読み込んでしまうと、 まず、所有していたポインタの指し先は開放されずにメモリリークが発生します。 また、読み込まれたm_nameの指し先をデシリアライザが管理していることも問題になります。
    そこで、指し先を管理しないポインタを宣言(2)して、デシリアライザを呼び出し、 あらかじめm_nameの指し先を開放(3)して、 deserializerが管理している指し先をコピーして設定(4)するようにします。

    m_nameへの設定でdup( )を使っているのは、p の指し先がdeserializerで管理されているからで、 m_nameが所有する文字列を新たに作っていることになります。
    上記コードで、p を宣言せずデシリアライザからm_nameに直接読み込んだり、 dup( ) を使わずにdeserializerが管理しているデータのアドレスをそのまま m_name に設定すると、 デシリアライザが管理しているデータへのポインタを、my_classでさらに管理することになります。 つまり、my_classのデストラクタと、デシリアライザのデストラクタによって、 二重のdelete []が発生することになり、ランタイム・エラーが発生してしまいます。
    なお、dup( ) は<omt/common.h>で定義される複製関数で、 C文字列に対してはstrdup( )と同様の処理(NULLはそのまま返す)を行います。

    ちなみに、道具箱コンテナへのデシリアライズでは、コンテナのごとのメモリ管理ポリシーを使って デシリアライザ側で領域の処理を行うため、メモリ管理を意識した特別な処理は必要ありません。

    では、以下にポインタのシリアライズ処理と、 指し先のデータ領域をdeserializerがどう管理するかについて、まとめておきます。

     シリアライズデシリアライズ
    char* または
    const char*
    C文字列としてシリアライズデシリアライザが指し先の文字列を保持
    その他の型へのポインタ指し先の型でシリアライズデシリアライザが指し先の値を保持
    配列へのポインタコード中で要素をひとつひとつ処理することが必要

    まず、シリアライザはポインタの型が char* または const char* の場合は、その値が0でなければ、 ヌル文字終端を持つCの文字列を指すものとしてその指し先を文字列として保持します。 (0の場合は、単にヌルポインタに相当する0を書き出します。)
    char* または const char* 以外のポインタ場合は、やはり0でなければ、ポインタの指し先のデータを保持します。 (したがって、Cの文字列以外の配列型のデータは、配列を指すポインタをそのままシリアライズすると、 その先頭要素のみの処理になるため、コード中で要素ひとつひとつをシリアライズする必要がある点に 注意が必要です。)

    Note:
    実際内部で行われる処理を解説すると、以下のようになります。

    シリアライズ処理では、ポインタの値は外部ストリームにシンボルとして書き出されますが、 この処理により同じ指し先を持つポインタはすべて同じシンボルとして書き出されることになります。 また、初めて処理されるアドレスへのポインタには、その指し先のデータがシリアライズされてつづきます。

    デシリアライザ側では、指し先のデータがあると、ポインタの指し先の変数を作成 (ここでデフォルト・コンストラクタが使われる)し、 その変数へのポインタを内部のハッシュテーブルに、 ポインタの値をもつシンボルをキーにして格納して管理します。 そして、デシリアライズされるポインタそのものには、 このハッシュに登録したのと同じアドレスを格納することになります。
    (ただし、データとそのデータへのポインタを同時にシリアライズした場合は、 ポインタの指し先がコピーされてデシリアライザに管理されるため、 ポインタは元のデータを指さないことに注意が必要です。このケースでポインタの同値性を確保する場合は、 すべてポインタとしてシリアライズ/デシリアライズするようにします。)

    このように、デシリアライザがポインタの指し先のデータの管理者として機能するため、 シリアライザとデシリアライザの機能は非対称になります。 しかしこのことによって、ポインタの指し先を管理していない場合でも、 デシリアライザの破棄のタイミングにさえ注意すれば、 アプリケーション・コードがポインタをデシリアライズをして、指し先を安全に扱うことができるのです。

  • シリアライザ(serializer)
  • シリアライザは、シリアライズ処理で作成される出力ストリームを扱うクラスです。
    実際のシリアライズ処理は、このシリアライザに対して、 シリアライズする変数をシリアライズ演算子( << )でつないで記述します。

    以下、シリアライザの概要について解説します。

    シリアライザの作成と破棄

    シリアライザ(serializer)クラスの変数は、シリアライズ処理の出力ストリームとして使用されます。 したがって、シリアライズ処理を行う前に、serializerのインスタンスを作成しなければなりません。
    serializerのコンストラクタは、

    のいずれかの引数をとり、その引数に対してシリアライズされた外部ストリームを出力します。
    引数を省略した場合は、FILE*に対してstdoutを渡したのと同じように振舞います。
    Cの文字列バッファに出力したい場合は、xstrかstd::stringに出力して、バッファに取り込めばよいでしょう。

    シリアライザの破棄自体には特に注意する点はありません。
    ただし、後述するように、C文字列を覗くポインタのシリアライズを行った場合、 ポインタの指し先が出力されるのは、このシリアライザの破棄のタイミングであることを注意してください。 ポインタのシリアライズ後、シリアライザの破棄までに指し先を変更すると、意図した結果と異なるデータが 出力されることになります。

    シリアライザのメンバ関数

    シリアライザの公開メンバ関数は多数ありますが、ほとんどがシリアライズ処理の際、シリアライズ演算子( << ) から呼び出されるものなので、次節で解説することにします。

    以下ではそれ以外のインターフェースを1つ紹介しましょう。

    	bool serializer::writeVersion( const char* ver, long seq );
    このインターフェースは、シリアライザを作成したすぐ後に呼び出され、 外部ストリームに引数に指定されたバージョン番号とメッセージ連番を出力します。
    このインターフェースを呼び出した場合は、 バージョン番号とメッセージ連番はデシリアライザでチェックされることになります。
    バージョン番号とメッセージ連番のチェックが不要であれば、呼び出す必要はありません。

    シリアライズ演算子

    シリアライズ処理は、作成したシリアライザに対して、 データをシリアライズ演算子( << )で出力する形で記述されます。
    演算子は左結合で、戻り値はシリアライズのリファレンスであるため、連続して記述すると左から順に出力されます。

    	serializer	sz;
    
    	sz << data;
    	sz << d1 << d2 << d3;

    演算子は様々なデータ型に対してオーバーロードされます。
    道具箱で定義している型やクラスだけでなく、ユーザー作成のクラスに対しても、 以下のテンプレート定義があるため、serializeメンバ関数を定義するだけで容易にオーバーロードをすることができます。

    	template<typename T>
    	serializer& operator<< ( serializer& out, const T& objref )
    	{
            	// serialize user defined class
           		objref.serialize( out );
            	return out;
    	}
    <omt/srz.h>内であらかじめ演算子オーバーロードが定義され、 変数をオペレータの右辺値とすることができる型を、外部ストリームへの出力別に分類すると以下のとおりになります。

    シリアライズ演算子のマニピュレータ

    シリアライズ処理を補助する目的で、4つのマニピュレータが提供されます。
    マニピュレータはシリアライズ対象の定数/変数と同様に演算子の右辺に置かれ、 シリアライザはこれを受け取ると、以下に述べるような特殊処理を行います。

  • デシリアライザ(deserializer)
  • デシリアライザは、 シリアライズ処理で作成された外部ストリーム・データを持つ入力ストリームを扱うクラスです。 また、デシリアライザは、ポインタへのデシリアライズ処理の際、 ポインタの指し先となるデータのコンテナの役割も果たします。
    実際のデシリアライズ処理は、このデシリアライザに対して、 デシリアライズされるデータを格納する変数を、 デシリアライズ演算子( >> )でつないで記述します。

    以下、デシリアライザの概要について解説します。

    デシリアライザの作成と破棄

    デシリアライザ(deserializer)クラスの変数は、デシリアライズ処理の入力ストリームとして使用されます。 したがって、デシリアライズ処理を行う前に、deserializerのインスタンスを作成しなければなりません。
    deserializerのコンストラクタは、

    のいずれかの引数をとり、その引数に対してシリアライズされた外部ストリームを出力します。
    引数を省略した場合は、FILE*に対してstdinを渡したのと同じように振舞います。
    Cの文字列バッファに出力したい場合は、xstrかstd::stringに出力して、バッファに取り込めばよいでしょう。

    シリアライザの破棄自体には特に注意する点はありません。
    ただし、後述するように、C文字列を覗くポインタのシリアライズを行った場合、 ポインタの指し先が出力されるのは、このシリアライザの破棄のタイミングであることを注意してください。 ポインタのシリアライズ後、シリアライザの破棄までに指し先を変更すると、意図した結果と異なるデータが 出力されることになります。

    デシリアライザのメンバ関数

    デシリアライザの公開メンバ関数は多数ありますが、多くはデシリアライズ処理の際、デシリアライズ演算子( >> ) から呼び出されるものなので、次節で解説することにします。

    以下、それ以外のデシリアライザの状態やエラー処理などに関わる補助的なメンバ関数について解説します。

    	bool deserializer::operator bool();
    現在のデシリアライザの状態をチェックします。
    デシリアライズ可能な場合はTRUEを、 読み込みの途中でエラーが発生したり、ストリームの終端を検知するとFALSEを返します。
    boolへの変換演算子として定義していますので、 デシリアライザの変数を、 if ( ds ) { ... } while ( ds ) { ... } のように、bool値を置くことができる条件節にそのまま書くことができます。

    	const char* deserializer::getError();
    外部ストリームのデシリアライズ中にエラーが発生して、 上記の operator bool() でFALSEが返った場合、 このインターフェースでエラーの内容を得ることができます。 外部ストリームが改行で区切られている場合は、エラー発生行を知ることができるため、 エラー個所の特定に役立てることができます。

    	long deserializer::checkVersion( const char* ver );
    シリアライザの作成直後、serializer::writeVersion() を使ってバージョン番号とメッセージ連番を書き出した場合は、 デシリアライザの作成直後、このインターフェースを呼び出してチェックを行います。
    ユーザー作成クラスをライブラリ化した場合などに、 誤って別バージョンのクラスが定義されたモジュールをリンクしシリアライズ/デシリアライズを行って、 解析の難しいランタイム・エラーが発生することがあります。 この機能を使って、バージョン・チェックを行うことで、 こうした問題を事前に回避することができます。
    インターフェースでは引き渡されたバージョンをチェックし、 一致しない場合はデシリアライザを無効にします。 また、メッセージ連番は戻り値として返されるので、プログラム内で処理することができます。

    デシリアライズ演算子

    デシリアライズ処理は、作成したデシリアライザから、 データにデシリアライズ演算子( >> )を使って、変数に対して入力する形で記述されます。
    通常は、シリアライズと同じ順序で演算子と変数を記述し、データを復元していきますが、 分岐や変換などのコードを加えた、より高度なデシリアライズ処理を記述することも可能です。
    演算子は左結合で、戻り値はデシリアライザのリファレンスであるため、 連続して記述すると左から順に処理されます。

    	deserializer	ds;
    
    	ds >> data;
    	ds >> d1 >> d2 >> d3;

    演算子は様々なデータ型に対してオーバーロードされます。
    道具箱で定義している型やクラスだけでなく、ユーザー作成のクラスに対しても、 以下のテンプレート定義があるため、 deserializeメンバ関数を定義するだけで容易にオーバーロードをすることができます。

    	template<typename T>
    	deserializer& operator>> ( deserializer& in, const T& objref )
    	{
            	// deserialize user defined class
           		objref.deserialize( in );
            	return in;
    	}
    <omt/srz.h>内であらかじめ演算子オーバーロードが定義され、 変数をオペレータの右辺値とすることができる型を、 外部ストリームにある入力単位のタイプ別に分類すると以下のとおりになります。

    デシリアライズ演算子のマニピュレータ

    デシリアライズ処理を補助する目的で、2つのマニピュレータが提供されます。
    マニピュレータはデシリアライズされる変数と同様に演算子の右辺に置かれ、 デシリアライザはこれを受け取ると、以下に述べるような特殊処理を行います。

    デシリアライズ・エラーの処理

    デシリアライズ処理は、シリアライズ処理の逆処理として 書き出しと同じ型の変数に同じ順序で読み込みを行えば、通常エラーは発生しません。 しかし、なんらかの理由で、読み込み先の変数と外部ストリームの内容が一致せず、 読み込みができなければエラーが発生します。
    また、外部ストリームの終端まで読み込んで、さらに読み込みを続けようと した場合にもエラーが発生します。

    エラーが発生すると、デシリアライザはその時点で読み込みを中止し、 読み込みバッファにはエラー内容が格納されます。 このエラー状態のデシリアライザに対して、さらにデシリアライズ処理を行おうとしても、 それ以上読み込みを行いません。

    エラーの発生は、deserializer::operator bool()(bool値へのキャスト) がFALSEになったことで確認できます。
    また、エラー内容はconst char* deserializer::getError(); を使って取得します。

    条件付きデシリアライズ

    デシリアライズ処理では、 読み込まれたデータによって処理を変更しながら進めていく条件付きデシリアライズが可能です。

    リストは外部ストリーム上ではリスト開始トークン '(' と リスト終了トークン ')' で囲まれており、 通常はコンテナ(plistなど)のシリアライズで作成されますが、 'begin' および 'end' マニピュレータでシリアライズする変数を囲んで書き出すことも可能です。
    デシリアライザには、このリスト開始/終了トークンをシリアライザと同様に 'begin' および 'end' マニピュレータで処理する方法の他に、

            bool beginList();
            bool endList();
    という2つのメンバ関数を利用する方法が提供されています。
    マニピュレータは、対応するリスト開始/終了トークンが外部ストリームにないと、 デシリアライザをエラー状態にしますが、 メンバ関数の場合は、次に読み込むトークンがリスト開始/終了でない場合は、 読み込みを行わずに戻り値としてFALSEを返します。

    また、外部ストリームのリスト以外の要素については、 以下のgetIf系メンバ関数によって処理を進めることができます。

            bool getIfStr( xstr& s );
            bool getIfBin( binary& s );
            bool getIfInt( long& n );
            bool getIfFlt( double& n );
            bool getIfBool();
            bool getIfNull();
    	template <typename T> bool getIfPtr( T*& p );
    これらのメンバ関数は、指定された要素のみ読み込み、それ以外のものは読み込まないため、 要素の種類によって処理を分岐させていくことができます。

  • 付録
  • 以下はシリアライザに関連してリリースしたヘッダファイルです。

    汎用ストリーム <omt/gstream.h>

    Cのファイル、文字列、C++の入出力ストリーム(ファイル・ストリーム、文字列ストリーム) C++の文字列を統一してストリームとして扱うためのラッパー・クラスです。
    C++の標準ライブラリにあるストリームは、 継承関係を使って同様の目的で使用できるように設計されていますが、

    といった問題があるため、シンプルで軽量なものとして実装することにしました。

    シリアライザ/デシリアライザを構成する際に使用できるだけでなく、 アプリケーションで使用するストリームに用いて、構成で入出力を変更することができます。 具体的な使用方法は、シリアライザの実装を参考にしてください。

    拡張文字列バッファ <omt/xbuf.h>

    std::stringの代わりに頻繁に使用されるxstrが定義されているヘッダファイルです。

    特にストリーム処理を行う場合、C++の文字列(std::string)には以下の2つの問題があるため、 あえて実装しました。

    xbufは、末尾への追加でバッファがいっぱいになると、バッファ・サイズを倍にして内容をコピーします。
    また、ポインタ指向スタイルのクラスとして、コピー・コンストラクタとコピー代入演算子をプライベート宣言していますので、 暗黙のコピーが発生することがありません。(コピーが必要な場合は、dup() を使わなくても、 文字列をとるコンストラクタ xbuf( const char_type* ) を使うことでコピーすることができるので、 コードの記述性も損ないません。

    xbuf自体は、文字種をテンプレート引数としたテンプレートです。そのため、xbufをcharに対してスペシャライズしたクラスをxstrとして定義してあります。
    1バイトまたはミックスバイト文字列を扱う場合は、このxstrを使用します。
    xstrの形でxbufのコンストラクタとメンバ関数を一覧しましょう。

    	// constructor
    	xstr::xstr( size_t sz = 0 );	// バッファサイズを指定してxstrを作ります
    					   引数を省略するとデフォルトの256バイトで作成されます
    	xstr::xstr( const char* s );	// C文字列をコピーしてxstrを作ります
    	xstr::xstr( const char* s, size_t sz );
    					// 文字列sの先頭からszバイトでxstrを作ります
    	// stream operation
    	void put( const char a );	// 文字aを末端に追加します
    	void append( const char* s );	// 文字列sを末端に追加します
    	void append( const char* s, size_t sz );
    					// 文字列sの先頭からszバイトを末端に追加します
    	void append( const xbuf& x );	// 拡張文字列xの内容をコピーして末端に追加します
    	
    	void set( const char* s );	// 内容をクリアして、文字列sをコピーします。
    	void set( const char* s, size_t sz );
    					// 内容をクリアして、文字列sの先頭からszバイトをコピーします
    	void int pop();			// 末端から1文字取り除きその文字を返します
    	void int peek() const;		// 末端の1文字を取り除かずに返します
    	void int get() const;		// 繰り返し呼び出すことで、先頭から1文字づつ読み込みます
    	void int unget() const;		// 直前のget()を行う前の状態に戻します(連続して呼び出せません)
    	bool is_eos() const;		// get()で格納された文字列を読みきった場合にtrueを返します
    
    	size_t tell() const;		// 現在のget()の読み込み位置を先頭からのオフセットで返します
    	void seek( size_t c );		// tellの戻り値を引数にとって、
    					   tellを呼び出した位置に読み込み位置を戻します
    	void clear();			// 格納文字列をクリアします
    	void reset();			// get()の読み込み位置を先頭に戻します
    
    	const char* buf() const;	// 格納バッファの先頭アドレスを返します
    					   この時内容の文字列は'\0'終端を持っていないかもしれません
    	const char* c_str() const;	// 格納バッファの先頭アドレスを返します
    					   内容の文字列は'\0'終端を持つことが保証されます
    	size_t length() const;		// 内容の文字列の長さを返します
    	size_t size() cosnt;		// バッファサイズを返します
    	size_t max_length() const;	// 現在の最大の格納文字列長(バッファサイズ-1)を返します
    
    	operator void*() const;		// is_eos()がTRUEの時、内容文字列の末端のアドレスを返します
    	operator bool() const;		// !is_eos()を返します
    	bool operator !() const;	// is_eos()を返します
    
    	char& operator[] ( size_t n );	// n が文字列長を超えなければその位置文字の、越えた場合は末端の
    					   '\0'のレファレンスを返します
    	const char& operator[] ( size_t n ) const;
    					// 上記オペレータのconst版です
    	xbuf& operator=( const char* s );
    					// set( s )を実行し、*thisを返します。
    
    	void reserve( size_t sz );	// szがバッファサイズを越えていれば、バッファサイズをszに拡張します
    					   拡張された場合、内容文字列のコピーが発生します
    

    以下は、<omt/srz.h>内に定義されているユーティリティ・クラスです。

    binaryクラス

    文字列とバイナリを区別するために導入されたクラスです。
    このクラスを使うことで、バイナリ・データのシリアライズ/デシリアライズ処理が可能になります。

    クラス自体はxstrをpublic継承しただけのものですので、xstrと同様に使うことができます。

    rowstreamクラス

    一部データを処理せずにシリアライザとデシリアライザをつなぐ(たとえば通信プロトコルのレイアリング)際や、 外部ストリームの一部を保存して、別途処理する際に使用するクラスです。
    このクラスの変数をシリアライズ/デシリアライズ演算子の右辺に置くと、 シリアライズ/デシリアライズ処理をする代わりに、serializer::copy()/deserialize::scan()を呼び出して、 外部ストリームにそのまま書き出し/からそのまま読み込みを行います。
    (したがって、rowstreamの内容は外部ストリームのシンタックスにしたがっている必要があります。)

    クラス自体はxstrをpublic継承しただけのものですので、xstrと同様に使うことができます。