目次
シリアライズ処理は、構造を持ったデータを、ストリームのような1次元のデータに変更する処理で、
逆処理のデシリアライズとともに、データの保存や通信になくてはならないものです。
C++標準ライブラリの出力/入力ストリームには、
基本型やstringなどごく限られたデータのシリアライズ/デシリアライズがサポートされますが、
実用には十分なものではありません。また、これをそのまま拡張しても、値指向スタイルを想定したものでは、
ポインターの処理などが困難になります。
そこで、道具箱(Open Middleware Toolkit)では、
ポインターの指し先のデータを含めたシリアライズ/デシリアライズが可能な
ユーティリティ・ライブラリ(以下、シリアライザと呼びます)を用意します。
このシリアライザは、以下のような特長をもっています。
開発者は、シリアライザを使用してシリアライズ/デシリアライズ処理を記述することで、 アプリケーション・プログラムで使用するデータを保存・通信するためのフォーマットを その都度設計・実装する必要から開放されます。
以下では、簡単な使用例を観たあとで、
の順に解説を行い、シリアライザの構成と利用方法を概観します。まず、簡単なコードでシリアライズ処理の概要を観てみましょう。
このコードでは、道具箱コンテナの
ハッシュ・テーブル
を使ったユーザー定義クラス(my_class)をシリアライズ処理に対応さぜ、
このクラスを複数プールするクラス(my_pool)に、
ファイルへの保管とロード機能を定義しています。
ユーザー定義クラスをシリアライズ処理に対応させるのに必要なことは、
#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;
}
上記の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基本正規表現を意味します。
versionとsequenceは作成された外部ストリームのIDに相当するものですが、必須ではなく、
アプリケーションごとに使用するかどうかを決めることができます。
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.-:_]*/
また、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*で作成する場合を考えてみましょう。
このケースでは、コンストラクタおよびデストラクタのコードからも解るように、
m_nameが指し先の領域を管理しています。
そのため、デシリアライザからm_nameに直接読み込んでしまうと、
まず、所有していたポインタの指し先は開放されずにメモリリークが発生します。
また、読み込まれたm_nameの指し先をデシリアライザが管理していることも問題になります。
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& );
};
そこで、指し先を管理しないポインタを宣言(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のコンストラクタは、
シリアライザの破棄自体には特に注意する点はありません。
ただし、後述するように、C文字列を覗くポインタのシリアライズを行った場合、
ポインタの指し先が出力されるのは、このシリアライザの破棄のタイミングであることを注意してください。
ポインタのシリアライズ後、シリアライザの破棄までに指し先を変更すると、意図した結果と異なるデータが
出力されることになります。
シリアライザの公開メンバ関数は多数ありますが、ほとんどがシリアライズ処理の際、シリアライズ演算子( << ) から呼び出されるものなので、次節で解説することにします。
以下ではそれ以外のインターフェースを1つ紹介しましょう。
このインターフェースは、シリアライザを作成したすぐ後に呼び出され、
外部ストリームに引数に指定されたバージョン番号とメッセージ連番を出力します。
bool serializer::writeVersion( const char* ver, long seq );
このインターフェースを呼び出した場合は、
バージョン番号とメッセージ連番はデシリアライザでチェックされることになります。
バージョン番号とメッセージ連番のチェックが不要であれば、呼び出す必要はありません。
シリアライズ処理は、作成したシリアライザに対して、
データをシリアライズ演算子( << )で出力する形で記述されます。
演算子は左結合で、戻り値はシリアライズのリファレンスであるため、連続して記述すると左から順に出力されます。
serializer sz;
sz << data;
sz << d1 << d2 << d3;
演算子は様々なデータ型に対してオーバーロードされます。
道具箱で定義している型やクラスだけでなく、ユーザー作成のクラスに対しても、
以下のテンプレート定義があるため、serializeメンバ関数を定義するだけで容易にオーバーロードをすることができます。
<omt/srz.h>内であらかじめ演算子オーバーロードが定義され、
変数をオペレータの右辺値とすることができる型を、外部ストリームへの出力別に分類すると以下のとおりになります。
template<typename T>
serializer& operator<< ( serializer& out, const T& objref )
{
// serialize user defined class
objref.serialize( out );
return out;
}
Note:
ただし、以下のものは、ポインタによる間接データ参照ではなく、効率を優先してデータそのものが
書き出されます。(そのため、ポインタの同値関係は保存されません)
シリアライズ処理を補助する目的で、4つのマニピュレータが提供されます。
マニピュレータはシリアライズ対象の定数/変数と同様に演算子の右辺に置かれ、
シリアライザはこれを受け取ると、以下に述べるような特殊処理を行います。
リストを構成するための開始と終端を出力します。
これはあくまでもコーディング・スタイルとしてですが、
クラスのシリアライズ処理(serializeメンバ関数)では、クラスのメンバのシリアライズを
このbeginとendで囲むようにします。この処理によって、クラスのインスタンスごとに
シリアライズされた出力がまとまり、外部ストリームの解析やコードのデバッグの際に役立ちます。
またコンテナを利用せずに、データをひとつひとつ処理しながら、リストを出力する際にも使うことができます。
serializer sz;
sz << begin << 1 << 2 << 3 << end; // output '(1 2 3)'
外部ストリームを改行によって区切ります。改行はデリミターとして使用される文字で、
外部ストリームの内容には影響を与えません。
改行をうまく使うことで、コードの検証やデバッグが行いやすくなります。
デシリアライザは、
シリアライズ処理で作成された外部ストリーム・データを持つ入力ストリームを扱うクラスです。
また、デシリアライザは、ポインタへのデシリアライズ処理の際、
ポインタの指し先となるデータのコンテナの役割も果たします。
実際のデシリアライズ処理は、このデシリアライザに対して、
デシリアライズされるデータを格納する変数を、
デシリアライズ演算子( >> )でつないで記述します。
以下、デシリアライザの概要について解説します。
デシリアライザ(deserializer)クラスの変数は、デシリアライズ処理の入力ストリームとして使用されます。
したがって、デシリアライズ処理を行う前に、deserializerのインスタンスを作成しなければなりません。
deserializerのコンストラクタは、
シリアライザの破棄自体には特に注意する点はありません。
ただし、後述するように、C文字列を覗くポインタのシリアライズを行った場合、
ポインタの指し先が出力されるのは、このシリアライザの破棄のタイミングであることを注意してください。
ポインタのシリアライズ後、シリアライザの破棄までに指し先を変更すると、意図した結果と異なるデータが
出力されることになります。
デシリアライザの公開メンバ関数は多数ありますが、多くはデシリアライズ処理の際、デシリアライズ演算子( >> ) から呼び出されるものなので、次節で解説することにします。
以下、それ以外のデシリアライザの状態やエラー処理などに関わる補助的なメンバ関数について解説します。
現在のデシリアライザの状態をチェックします。
bool deserializer::operator bool();
デシリアライズ可能な場合はTRUEを、
読み込みの途中でエラーが発生したり、ストリームの終端を検知するとFALSEを返します。
boolへの変換演算子として定義していますので、
デシリアライザの変数を、
if ( ds ) { ... }
や while ( ds ) { ... }
のように、bool値を置くことができる条件節にそのまま書くことができます。
外部ストリームのデシリアライズ中にエラーが発生して、
上記の const char* deserializer::getError();
operator bool()
でFALSEが返った場合、
このインターフェースでエラーの内容を得ることができます。
外部ストリームが改行で区切られている場合は、エラー発生行を知ることができるため、
エラー個所の特定に役立てることができます。
シリアライザの作成直後、serializer::writeVersion() を使ってバージョン番号とメッセージ連番を書き出した場合は、
デシリアライザの作成直後、このインターフェースを呼び出してチェックを行います。
long deserializer::checkVersion( const char* ver );
ユーザー作成クラスをライブラリ化した場合などに、
誤って別バージョンのクラスが定義されたモジュールをリンクしシリアライズ/デシリアライズを行って、
解析の難しいランタイム・エラーが発生することがあります。
この機能を使って、バージョン・チェックを行うことで、
こうした問題を事前に回避することができます。
インターフェースでは引き渡されたバージョンをチェックし、
一致しない場合はデシリアライザを無効にします。
また、メッセージ連番は戻り値として返されるので、プログラム内で処理することができます。
deserializer ds;
ds >> data;
ds >> d1 >> d2 >> d3;
演算子は様々なデータ型に対してオーバーロードされます。
道具箱で定義している型やクラスだけでなく、ユーザー作成のクラスに対しても、
以下のテンプレート定義があるため、
deserializeメンバ関数を定義するだけで容易にオーバーロードをすることができます。
<omt/srz.h>内であらかじめ演算子オーバーロードが定義され、
変数をオペレータの右辺値とすることができる型を、
外部ストリームにある入力単位のタイプ別に分類すると以下のとおりになります。
template<typename T>
deserializer& operator>> ( deserializer& in, const T& objref )
{
// deserialize user defined class
objref.deserialize( in );
return in;
}
Note:
ただし、以下のものは、ポインタによる間接データ参照ではなく、効率を優先してデータそのものが
書き出されているので、ラベルを使わず、ポインタ作成とデータ読み込みが同時に行われます。
デシリアライズ処理を補助する目的で、2つのマニピュレータが提供されます。
マニピュレータはデシリアライズされる変数と同様に演算子の右辺に置かれ、
デシリアライザはこれを受け取ると、以下に述べるような特殊処理を行います。
リストを構成するための開始と終端を入力します。
これは、道具箱コンテナのようにリストを一括読み込みする変数に対してではなく、
手作業で1つ1つデータを読む時に使用します。
外部ストリームに対応するリスト開始および終端がない場合は、デシリアライザのステータスをエラーにします。
デシリアライズ処理は、シリアライズ処理の逆処理として
書き出しと同じ型の変数に同じ順序で読み込みを行えば、通常エラーは発生しません。
しかし、なんらかの理由で、読み込み先の変数と外部ストリームの内容が一致せず、
読み込みができなければエラーが発生します。
また、外部ストリームの終端まで読み込んで、さらに読み込みを続けようと
した場合にもエラーが発生します。
エラーが発生すると、デシリアライザはその時点で読み込みを中止し、 読み込みバッファにはエラー内容が格納されます。 このエラー状態のデシリアライザに対して、さらにデシリアライズ処理を行おうとしても、 それ以上読み込みを行いません。
エラーの発生は、deserializer::operator bool()
(bool値へのキャスト)
がFALSEになったことで確認できます。
また、エラー内容はconst char* deserializer::getError();
を使って取得します。
デシリアライズ処理では、 読み込まれたデータによって処理を変更しながら進めていく条件付きデシリアライズが可能です。
リストは外部ストリーム上ではリスト開始トークン '(' と リスト終了トークン ')' で囲まれており、
通常はコンテナ(plistなど)のシリアライズで作成されますが、
'begin' および 'end' マニピュレータでシリアライズする変数を囲んで書き出すことも可能です。
デシリアライザには、このリスト開始/終了トークンをシリアライザと同様に 'begin' および 'end'
マニピュレータで処理する方法の他に、
という2つのメンバ関数を利用する方法が提供されています。
bool beginList();
bool endList();
マニピュレータは、対応するリスト開始/終了トークンが外部ストリームにないと、
デシリアライザをエラー状態にしますが、
メンバ関数の場合は、次に読み込むトークンがリスト開始/終了でない場合は、
読み込みを行わずに戻り値として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 );
以下はシリアライザに関連してリリースしたヘッダファイルです。
Cのファイル、文字列、C++の入出力ストリーム(ファイル・ストリーム、文字列ストリーム)
C++の文字列を統一してストリームとして扱うためのラッパー・クラスです。
C++の標準ライブラリにあるストリームは、
継承関係を使って同様の目的で使用できるように設計されていますが、
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>内に定義されているユーティリティ・クラスです。
文字列とバイナリを区別するために導入されたクラスです。
このクラスを使うことで、バイナリ・データのシリアライズ/デシリアライズ処理が可能になります。
クラス自体はxstrをpublic継承しただけのものですので、xstrと同様に使うことができます。
一部データを処理せずにシリアライザとデシリアライザをつなぐ(たとえば通信プロトコルのレイアリング)際や、
外部ストリームの一部を保存して、別途処理する際に使用するクラスです。
このクラスの変数をシリアライズ/デシリアライズ演算子の右辺に置くと、
シリアライズ/デシリアライズ処理をする代わりに、serializer::copy()/deserialize::scan()を呼び出して、
外部ストリームにそのまま書き出し/からそのまま読み込みを行います。
(したがって、rowstreamの内容は外部ストリームのシンタックスにしたがっている必要があります。)
クラス自体はxstrをpublic継承しただけのものですので、xstrと同様に使うことができます。