boost C++ Libraries(以下単にboost)という非常に便利なライブラリがあります。 次期C++標準にこのライブラリのうちのいくつかが採用されるらしいという話もあり、 C++開発者にとっては無視して通れないライブラリです。
そんなboostの中に、serializationというライブラリがあります。 一言で言ってしまえば、「クラスの状態をファイルに書き出したり、復元するためのライブラリ」といったところでしょうか。 MFCのCArchiveクラスを使ったことのある方にはおなじみだと思います。 04/07/21現在、まだ正式版には含まれてはいませんが、いずれ含まれるでしょうということで、 ちょいと先走って使い方の解説をします。 願わくば、この記事を書いた後に大幅な変更などがありませんよう……
あと、このserializationって名前長くてタイプするの面倒なんですが。 なんかもちっと短い名前なかったのかなーと思ってしまう次第です。
このライブラリは、regexやfilesystemと同じように、ビルドしないと使用できません。
具体的なビルドの仕方は、こちらをご覧ください。
boost::serializationを使用したプログラムのコンパイルの際には、 明示的にライブラリファイルをリンクしてやる必要があるようです。 ここの命令規則とにらめっこしながら、 適切なライブラリファイルをリンクしてください。
基本的に必要なのは、(lib)boost_serialization_*.libで、 ワイド文字列のアーカイブを使う場合にはそれに加えて(lib)boost_wserialization_*.libが必要となるようです。
まずは簡単な例題として、下のようなStudentクラスをシリアル化する場合のことを考えてみます。 例題のため、極限まで簡略化してあります。
001 002 #include <string> 003 004 class Student { 005 private: 006 std::string name_; 007 int age_; 008 };
このクラスに、シリアル化できるようにコードを書き加えたものが以下のコードです。
001 002 #include <string> 003 #include <boost/serialization/string.hpp> 004 005 class Student { 006 private: 007 std::string name_; 008 int age_; 009 010 friend class boost::serialization::access; 011 template<class Archive> 012 void serialize(Archive& ar, const unsigned int version) 013 { 014 ar & name_; 015 ar & age_; 016 } 017 };
それぞれの追加行について説明していきます。
003 #include <boost/serialization/string.hpp>
std::stringクラスをシリアル化するために必要なヘッダーです。 同様に、例えばstd::listクラスをシリアル化するのならばboost/serialization/list.hppが必要になります。 このように特別のインクルードが必要なクラスを以下に挙げます。
それぞれ必要なヘッダー名は、boost/serialization/<クラス名>.hppです。 私としてはC++標準にもかかわらず無視されているvalarrayとbitsetが可哀想で可哀想で……。
010 friend class boost::serialization::access;
boost::serializationライブラリが、privateメンバであるserialize関数にアクセスするためのfriend指定です。 おまじないみたいなものだと考えてください。 なお、このおまじないが面倒だからといって、serialize関数をpublicにしてはいけません。理由は後述します。
011 template<class Archive> 012 void serialize(Archive& ar, const unsigned int version) 013 { 016 }
arは、出力先・入力元を表す引数です。 出力先・入力元の形式には、テキスト形式や、バイナリ形式、XML形式などの種類がありますが、 その種類の違いをテンプレート引数Archiveで吸収しています。
versionは、保存形式のバージョンを示す数値です。 アプリケーションを開発していくにつれて、保存・読込をするデータも変わってくることに備えて、 保存形式のバージョンをチェックしておく必要があるかもしれません。
014 ar & name_; 015 ar & age_;
それぞれのメンバをarに出力・読込します。 C++のストリームなどでは、出力に<<演算子、読込に>>演算子を使いますが、 boost::serializationでは両方あわせて&演算子を使います (<<演算子や>>演算子も使えます。が、この関数は出力と入力両方を受け持つので&演算子を使います)。
実際にデータを入出力するときは、以下のようにします。ここでは、テキスト形式での入出力を説明します。
001 #include <fstream> 002 #include <boost/archive/text_oarchive.hpp> 003 #include <boost/archive/text_iarchive.hpp> 004 005 void hoge(void) { 006 Student student; 007 008 // 出力アーカイブの作成 009 std::ofstream ofs("output.txt"); 010 boost::archive::text_oarchive oa(ofs); 011 012 // ファイルに書き出し 013 oa << student; 014 015 // 出力を閉じる 016 ofs.close(); 017 018 // 入力アーカイブの作成 019 std::ifstream ifs("output.txt"); 020 boost::archive::text_iarchive ia(ifs); 021 022 // ファイルから読込 023 ia >> student; 024 025 // 入力を閉じる 026 ifs.close(); 027 }
大まかな流れとしては、出(入)力ストリームを開いて、それをアーカイブクラスのコンストラクタに渡してアーカイブを作り、 そのアーカイブにデータを出(入)力する、という流れになります。
アーカイブクラスの後始末は不要なようです。ストリームも、ほっとけばいずれ閉じますので、 わざわざcloseしなくても大丈夫そうです。例では直後にifstreamを使う関係上閉じてますが。
なお、入出力形式にはテキスト形式、バイナリ形式、XML形式の3種類があります。 それぞれ対応するアーカイブクラス名を列挙します。
また、テキストアーカイブとXMLアーカイブには、それぞれワイド文字版が用意されています。 ワイド文字版は、コンストラクタにwstreamをとります。
xml系のアーカイブを使用する場合には、後述するNVPを使用する必要があります。 また、ワイド文字列版でないxmlアーカイブは、 出力時に文字コードがUTF-8でなく、OSネイティブな文字コードで保存されます。 にもかかわらず、xml中のエンコーディング指定でUTF-8が指定されていますので、 正しいxmlファイルではないということに留意してください。
先程のStudentクラスに、新たにメンバ変数を追加した場合のことを考えてみます。 ここでは、生徒の身長を表すheight_メンバ変数を追加してみましょう。
001 002 #include <string> 003 004 class Student { 005 private: 006 std::string name_; 007 int age_; 008 int height_; 009 };
このクラスを、単純にserialization対応にすると以下のようになります。
001 002 #include <string> 003 #include <boost/serialization/string.hpp> 004 005 class Student { 006 private: 007 std::string name_; 008 int age_; 009 int height_; 010 011 friend class boost::serialization::access; 012 template<class Archive> 013 void serialize(Archive& ar, const unsigned int version) 014 { 015 ar & name_ & age_ & height; 016 } 017 };
前回とほとんど同じですので、細かい説明は省きます。 前回説明はしませんでしたが、このように1行に続けて&演算子を使用することも可能です。
この拡張されたStudentクラスを仮にVer.1、前回使用したStudentクラスをVer.0とおきます。 ここで問題としたいのは、Ver.1のStudentクラスで、Ver.0で保存したデータを読み込みたい場合はどうするのか、ということです。
こんな状況のために、引数versionがあるのでしたね。引数versionの値に応じて動作を変えるように書き換えてみましょう。
001 002 #include <string> 003 #include <boost/serialization/string.hpp> 004 #include <boost/serialization/version.hpp> 005 006 class Student { 007 private: 008 std::string name_; 009 int age_; 010 int height_; 011 012 friend class boost::serialization::access; 013 template<class Archive> 014 void serialize(Archive& ar, const unsigned int version) 015 { 016 if(version > 0) { 017 ar & name_ & age_ & height_; 018 } 019 else { 020 ar & name_ & age_; 021 } 022 } 023 }; 024 025 BOOST_CLASS_VERSION(Student, 1);
変化した部分を、詳しく見ていきましょう。
004 #include <boost/serialization/version.hpp> 025 BOOST_CLASS_VERSION(Student, 1);
クラスにバージョン付けをするには、BOOST_CLASS_VERSIONマクロを使用します。 ここでは、クラスStudentに1というバージョンをつけています。 これによって、関数serializationに渡されるversion引数が変化します。 クラスの内容が変化するたびに、このバージョン番号を1ずつ増やしていけばいいのですね。
基本的に、serialization()関数に渡されるversion引数は、 書き込み時にはBOOST_CLASS_VERSIONマクロで指定された最新のバージョン、 読み込み時には読み込んでいるアーカイブのバージョン(当然現バージョンよりも古いかもしれません)が渡されます。
boost::serializationライブラリでは、「クラスごとに」バージョン付けをします。 そのため、ファイルの保存形式そのものにはバージョン付けをする必要はありません。
016 if(version > 0) { 017 ar & name_ & age_ & height_; 018 } 019 else { 020 ar & name_ & age_; 021 }
ここで、バージョンによって処理を分岐させています。 具体的には、バージョン1より前ならname_とage_を読み書きし、 バージョン1以降ならそれに加えてheight_も読み書きします。
さて、前回の例ですが、よくよく考えてみると、書き込み時にはバージョンのことを考える必要はなさそうです。 だって、書き込むときは常に最新のバージョンなんですから。
そこのところを考えて、書き換えたコードが以下のものになります。
001 002 #include <string> 003 #include <boost/serialization/string.hpp> 004 #include <boost/serialization/version.hpp> 005 #include <boost/serialization/split_member.hpp> 006 007 class Student { 008 private: 009 std::string name_; 010 int age_; 011 int height_; 012 013 friend class boost::serialization::access; 014 BOOST_SERIALIZATION_SPLIT_MEMBER(); 015 016 template<class Archive> 017 void save(Archive& ar, const unsigned int version) const 018 { 019 ar & name_ & age_ & height; 020 } 021 022 template<class Archive> 023 void load(Archive& ar, const unsigned int version) 024 { 025 if(version > 0) { 026 ar & name_ & age_ & height_; 027 } 028 else { 029 ar & name_ & age_; 030 } 031 } 032 }; 033 034 BOOST_CLASS_VERSION(Student, 1);
boost::serializationでは、このように書き込み時と読み込み時で別々の関数に分岐させることもできます。 以下に各追加行の説明をします。
005 #include <boost/serialization/split_member.hpp> 014 BOOST_SERIALIZATION_SPLIT_MEMBER();
読み書きを別々の関数に分離するには、BOOST_SERIALIZATION_SPLIT_MEMBERマクロを使用します。 このマクロを使用することで、serialization関数をload関数とsave関数に分割することができます。
016 template<class Archive> 017 void save(Archive& ar, const unsigned int version) const 018 { 019 ar & name_ & age_ & height; 020 }
save関数は、書き込み時に呼び出されます。 気づきにくいですが、書き込みだけでクラスの状態は変化しないのでconst関数になっています。
022 template<class Archive> 023 void load(Archive& ar, const unsigned int version) 024 { 025 if(version > 0) { 026 ar & name_ & age_ & height_; 027 } 028 else { 029 ar & name_ & age_; 030 } 031 }
load関数は、読み込み時に呼び出されます。特に前回のserialization関数と違いはありません。
さて、今までの方法では、シリアル化したいクラスにはメンバ関数を追加する必要がありました。 自分で作ったクラスならそれでもいいのですが、他人が作ったクラスやライブラリに含まれるクラスなど、 おいそれとはメンバ関数を追加できない場合も多々存在します。 今回は、そんな場合のための方策、「非侵入型(non-intrusive)」のシリアル化関数を紹介します。
では、やってみましょう。今回はWin32 SDKのPOINT構造体をシリアル化してみます。POINT構造体は、こんな感じの構造体です。
001 struct POINT { 002 LONG x; 003 LONG y; 004 };
非常に単純な構造体ですが、windows.h内で宣言されているためおいそれとは変更できません。 このPOINT構造体をシリアル化したい場合は、以下のようなコードを適当なファイルに追加します。 POINT構造体をシリアル化するすべての部分からこの関数が参照できないといけませんので、 共通ヘッダーあたりに置くのがベストだと思います。
001 namespace boost { 002 namespace serialization { 003 template <class Archive> 004 void serialize(Archive& ar, POINT& rpoint, const unsigned int version) 005 { 006 ar & rpoint.x & rpoint.y; 007 } 008 } 009 }
細かい説明は不要でしょう。boost::serialization名前空間内に配置しなければならない点がポイントです。
これで、POINT構造体もシリアル化できるようになりました。 このような実装の実例がboost/serialization/string.hppやboost/serialization/list.hppなどにありますので、参考にするとよいでしょう。
この項で言いたいことは一つだけです。
「基底クラスのserialize関数を直接呼び出さないこと!!」
はい、復唱。
「基底クラスのserialize関数を直接呼び出さないこと!!」
覚えましたね? 具体例を挙げて説明します。Studentクラスから派生したHighSchoolStudentクラスで考えましょう。
001 002 class HighSchoolStudent : public Student { 003 private: 004 int grade_; 005 };
Studentクラスに学年を表すgrade_メンバ変数を加えただけのとても単純なクラスです。 これをserialization対応に書き直します。
001 002 #include <boost/serialization/base_object.hpp> 003 004 class HighSchoolStudent : public Student { 005 private: 006 int grade_; 007 008 friend class boost::serialization::access; 009 template <class Archive> 010 void serialize(Archive& ar, const unsigned int version) 011 { 012 ar & boost::serialization::base_object<Student>(*this); 013 ar & grade_; 014 } 015 };
問題となるのは、以下の部分です。
002 #include <boost/serialization/base_object.hpp> 012 ar & boost::serialization::base_object<Student>(*this);
12行目で、基底クラスのシリアル化を行なっています。 繰り返しますが、直接基底クラスのserialization関数を呼び出すのでなく、base_object関数を使用してください。 クラスのバージョン付けなどの面で不具合が発生します。 serialization関数をprivateとして宣言するのも、不用意に派生クラスから直接呼び出されないようにするための処置です。
配列は、そのままシリアライズ可能です。
001 class Homeroom { 002 private: 003 Student[40] students_; 004 005 friend class boost::serialization::access; 006 template <class Archive> 007 void serialize(Archive& ar, const unsigned int version) 008 { 009 ar & students_; 010 } 011 };
なんかもう説明するまでもないくらいシンプルです。
constメンバは、クラスのコンストラクタでしか初期化できず、 初期化したら最後二度と変更できません。 このconstメンバをどうするかということですが……。 説明書を読むと、const_castでごり押ししろ(意訳)らしいです。こんな感じでしょうか。
001 class Student { 002 private: 003 int age_; 004 const bool sex_; 005 006 friend class boost::serialization::access; 007 template <class Archive> 008 void serialization(Archive& ar, const unsigned int version) 009 { 010 ar & age_; 011 ar & const_cast<bool&>(sex_); 012 } 013 };
最近は性別をconstにすると人権団体から怒られたりするんでしょうか。ちょっとどきどきです。
こういうときのためのconst_castです。 注意すべき点としては、この関数は読み書き双方に使われるので、 boolにではなくbool&型にキャストしなければならない、ということでしょうか。
解説書のこの頁に載っていた言葉が印象的だったので、引用しておきます。
Note that this violates the spirit and intention of the const keyword. const members are intialized when a class instance is constructed and not changed thereafter. However, this may be most appropriate in many cases. Ultimately, it comes down to the question about what const means in the context of serialization.
(適当な訳) この処置はconstを使用する精神と意図を破壊するものであることに留意してください。 constメンバはクラスが実体化されたときに初期化され、それ以後変更されることはありません。 しかし、このconst_castを使用する処置は多くの場合に適切です。 究極的には、これはconstというキーワードがシリアライズという文脈の中でどのような意味を持つか、 という問題に帰結します。
要するに、あんまし深く突き詰めて考えちゃだめだよってことですね。宗教戦争になりますよ、と。
単純型へのポインタや、デフォルトコンストラクタのあるクラスへのポインタの場合、 今までと同様にシリアライズが可能です。
001 class Teacher { 002 private: 003 Homeroom* phomeroom_; 004 005 friend class boost::serialization::access; 006 template <class Archive> 007 void serialization(Archive& ar, const unsigned int version) 008 { 009 ar & phomeroom_; 010 } 011 };
保存の時にポインタの先を辿って保存してくれるのはもちろん、 読み込み時には勝手にnewしてインスタンスを作ってくれて、 複数のポインタから指されているオブジェクトも適切にシリアライズしてくれて、 さらにインスタンスが派生クラスでもちゃんと対応してくれます (それなりの手続きをしていれば、ですけど……後述)。すごい。
では、デフォルトコンストラクタのないクラスへのポインタをシリアライズしたい場合はどうするのでしょうか。 こちらは、ちょっと手順を踏む必要があります。
001 class Chair { 002 public: 003 Chair(int height) : height_(height) {} 004 int getHeight(void) const { return height_; } 005 private: 006 int height_; 007 008 friend class boost::serialization::access; 009 template <class Archive> 010 void serialization(Archive& ar, const unsigned int version) 011 { 012 ar & height_; 013 } 014 };
デフォルトコンストラクタを持たないクラスChairについて考えます。 とりあえず、基本的なところまでは一通り書きました。
あとは、以下の関数を追加する必要があります。
001 namespace boost { 002 namespace serialize { 003 template <class Archive> 004 inline void save_construct_data ( 005 Archive& ar, const Chair* p, const unsigned long int version) 006 { 007 ar << p->getHeight(); 008 } 009 010 template <class Archive> 011 inline void load_construct_data ( 012 Archive& ar, Chair* p, const unsigned long int version) 013 { 014 int height; 015 ar >> height; 016 ::new(p) Chair(height); 017 } 018 } 019 }
この関数は、boost::serialize名前空間内に定義する必要がある点に注意してください。
010 template <class Archive> 011 inline void load_construct_data ( 012 Archive& ar, Chair* p, const unsigned long int version) 013 { 017 }
この関数は、インスタンスのメモリは確保したけどコンストラクタはまだ呼んでいない、という段階で呼び出されます。 この関数内で、コンストラクタを呼び出し、正しくメモリ上にデータを配置する必要があるわけです。
デフォルトの実装では、デフォルトコンストラクタを呼んでいるのですが、それが存在しない場合、 このようにオーバーロードしてやる必要があるのです。
003 template <class Archive> 004 inline void save_construct_data ( 005 Archive& ar, const Chair* p, const unsigned long int version) 006 { 007 ar << p->getHeight(); 008 }
この関数は、load_construct_data()関数と対になる関数です。 load_construct_data()関数で必要となるデータを、アーカイブに書き出しています。
014 int height; 015 ar >> height; 016 ::new(p) Chair(height);
コンストラクタに必要な情報を読み出した上で、コンストラクタを呼び出しています。 ここで、16行目の処理は所謂「配置new(placement new)」というもので、 More Effective C++に詳しく載っているのですが、お持ちでない方はこちらのページがわかりやすいと思います。
ともあれ、これでデフォルトコンストラクタを持たないクラスでも ポインタを通じてシリアライズができるようになりました。バンザイ。
まずは、以下のコードをご覧ください。
001 class Base { 002 ... // serialize()関数など 003 }; 004 005 class Derived : public Base { 006 ... // serialize()関数など 007 };
Baseクラスから派生したDerivedクラスがあるとします。 どっちのクラスにも、serialize()関数などシリアライズに必要な関数は一通りそろっているとして考えます。
このとき、以下のような処理は当然可能です。
001 Base* p = new Derived(); 002 ar << p;
これは、前の項で説明したとおり、ポインタの指す先をたどり、 正しくDerivedクラスのserialize()関数を呼び出してくれます。
では、その保存したデータを読み出す場合はどうでしょう。
001 Base* p; 002 ar >> p;
読み出すデータは、Derived型です。serializationライブラリは、 正しくDerived::serialize()関数を呼んでくれるでしょうか?
答えから言うと、「前後の記述如何では正しく呼んでくれるかもしれないし、呼んでくれないかもしれない」です。 この読み込みが行われる時点で、serializationライブラリにDerivedクラスが「登録」されているかどうかが、 運命の分かれ道となります。
簡単に登録の仕組みを説明します。アーカイブを通して、読み出し・書き込みが行われたクラスは、 それぞれ読み書きされた順に通し番号を振られて、登録されます。 そして登録されたクラスは、 基底クラスへのポインタからの復元(ちょうど今回の例のようなケースです)ができるようになります。 逆に言えば、登録されていないクラスは、 基底クラスへのポインタからの復元はできません。
よって、上記の例が成功するかどうかは、それまでにDerivedクラスを読み書きしたことがあるかどうかにかかっているのです。
しかし、それではあまりにも不便です。 そこで、実際に読み書きを行わなくても登録ができるシステムが2つ用意されています。
text_iarchiveやbinary_oarchiveなどのアーカイブクラスには、 すべてregister_type()関数が用意されています。 この関数は、名前のとおりクラスを登録します。簡単に使い方の例を見てみましょう。
001 ar.register_type<Derived>(); 002 // 上の行がコンパイル通らない場合は、こちらを試してみてください。 003 // ar.template register_type<Derived>(); 004 Base* p; 005 ar >> p;
テンプレート引数Tにクラス名を渡します。 これで、Derivedクラスが登録されます。
ところでこの方式に限らないのですが、save/load関数に分割したときは、 可能な限り関数の「対称性」を保つようにしてください。 どういう意味かと言いますと、登録したときに割り振られる番号は、シーケンシャルに割り振られます。 つまり登録の順番がずれると、番号もずれてしまうのです。 書き込み/読み込みで番号のずれがあると、正しく復元をすることができません。 よって、load関数でregister_type()関数を呼び出すときは、 save関数でも同じ場所で呼び出しておく必要があります。
もうひとつの方法が、このBOOST_CLASS_EXPORT_GUIDマクロです。 個人的にはこちらの使用をお勧めします。
001 #include <boost/serialization/export.hpp> 002 003 class Derived : public Base { 004 ... // serialize()関数など 005 }; 006 007 BOOST_CLASS_EXPORT_GUID(Derived, "Derived");
このマクロを使うと、クラスTが登録され、 さらに通し番号の代わりに与えられた文字列Kが使用されます。 文字列は、一意なものであれば何でも構いません。 グローバルスコープで登録されるので、登録されるタイミングをいちいち考えなくてよかったり、 クラスのヘッダーに書いておけばクラスの利用者側でいちいち登録に気を使う必要がないといった利点があります。
これと似たマクロで、BOOST_CLASS_EXPORTマクロがあります。 BOOST_CLASS_EXPORT_GUIDマクロの第二引数に自動的にクラス名を入れてくれるマクロで、 BOOST_CLASS_EXPORT(Derived)のようにして使用します。
ちなみに、これらの処理を怠って、未登録の状態で基底クラスへのポインタから復元しようとした場合、 unregistered_class例外が投げられます。
serializationライブラリでは、デフォルトの設定ではクラスを保存する際に型情報なども同時に保存します。 これのおかげで派生クラスなどを適切にシリアライズできるのですが、 時々これではオーバースペックだと感じることがあります。
例えば、Win32SDKに含まれるRGBQUAD構造体。 派生クラスも特になく、メンバもバイト型4つだけという非常に単純な構造体です。
001 struct RGBQUAD { 002 BYTE rgbBlue; 003 BYTE rgbGreen; 004 BYTE rgbRed; 005 BYTE rgbReserved; 006 };
はたしてこの構造体をシリアライズするのに、型情報を保存する必要があるでしょうか? しかもこのクラスは、最大で256個連続して保存される可能性があるのです。 クラス情報の保存によるオーバーヘッドは莫迦になりません。
そんなときのために、serializationライブラリには「実装レベル(Implementation level)」というものが用意されています。
001 002 #include <booost/serialization/level.hpp> 003 004 BOOST_CLASS_IMPLEMENTATION(RGBQUAD, boost::serialization::object_class_info);
BOOST_CLASS_IMPLEMENTATIONマクロを使用することでクラスごとの実装レベルを変更することが可能です。 このマクロの第二引数で実装レベルを指定します。 実装レベルには、次の4種類があります。
これを指定したクラスは、シリアル化しません。 &演算子などでアーカイブに流し込まれても、無視されます。 volatile指定された場合、デフォルトでこの設定になるようです。
enum型や組み込み型に適用される実装レベルです。 serialize関数を通さずに、直接メモリ上のデータがアーカイブに流し込まれます。
serialize()関数でシリアライズしますが、型情報やバージョン情報は保存しません。 軽くしたい場合は、基本的にこれを指定しましょう。primitive_levelはコンパイラ間の互換性に疑問が残ります。
型情報やバージョン情報も保存します。一般クラスは、デフォルトではこの設定になっています。
ポインタを通してシリアライズする場合、複数のポインタから指されているオブジェクトについて考える必要があります。 どういうことかといいますと、例えばポインタAとBから指されているオブジェクトαがあったとして、 何も考えずにAとBをシリアライズするとαが2回保存されてしまうことになります。
このような挙動を避けるため、serializationライブラリは自動的にオブジェクト追跡(Object tracking)という機能を用いて、 オブジェクトの二重保存を防いでいます。
でも当然この機能も、オーバーヘッド(主に速度・メモリ面でしょうか)と引き換えに成り立っているわけでして。 上記のような心配がない場合は、この機能も任意にオフにすることができます。
例として、先ほど挙げたRGBQUAD構造体を再び使用します。
001 002 #include <boost/serialization/tracking.hpp> 003 004 BOOST_CLASS_TRACKING(RGBQUAD, boost::serialization::track_never);
BOOST_CLASS_TRACKINGマクロを使用することで任意に追跡機能をオンオフできます。 第二引数で追跡機能のオンオフを指定します。指定できる値は、次の三つです。
追跡機能をオフにします。組み込み型は、デフォルトでこの設定になっています。
ポインタからシリアライズされた場合にのみ、追跡機能を使用します。 デフォルトではこの設定になっています。
常に追跡機能をオンにします。 いまいちメリットがよくわかりませんが……。
抽象クラスへのポインタを通して派生クラスをシリアライズしようとした場合、 コンパイルエラーが発生することがあります。 このエラーを消すためには、BOOST_IS_ABSTRACTマクロを使用します。
001 002 #include <boost/seralization/is_abstract.hpp> 003 004 BOOST_IS_ABSTRACT(hogehoge);
例では、hogehogeクラスが抽象クラスであるとserializationライブラリに通知しています。
アーカイブにXML形式を使用した場合、要素の値だけでなく要素名が必要となります。 そのために用意されている仕組みが「NVP」です。 std::mapみたいな要素名と値との対応付けですね。
001 002 #include <boost/serialization/nvp.hpp> 003 004 class HighSchoolStudent : public Student{ 005 private: 006 int weight_; 007 int height_; 008 009 friend class boost::serialization::access; 010 template <class Archive> 011 void serialization(Archive& ar, const unsigned int version) 012 { 013 ar & boost::serialization::make_nvp("weight", weight_); 014 ar & BOOST_SERIALIZATION_NVP(height_); 015 ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Student); 016 } 017 };
このようにして要素名と要素のペアをアーカイブに与えると、 アーカイブがXML形式のときは<要素名>要素</要素名>のように出力し、 アーカイブがXML以外の形式のときは要素名を無視します。
002 #include <boost/serialization/nvp.hpp> 013 ar & boost::serialization::make_nvp("weight", weight_);
第一引数で要素名、第二引数で要素を指定します。 要素名を自由に指定できる自由度の高い形式です。
002 #include <boost/serialization/nvp.hpp> 014 ar & BOOST_SERIALIZATION_NVP(height_);
要素名を変数名から自動的に決定してくれるマクロです。 タイプする手間を省く目的なのかもしれませんが、 boost::serializationをusingしていて変数名が11文字以内ならmake_nvp関数で指定したほうが早いです……。
002 #include <boost/serialization/nvp.hpp> 015 ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Student);
以下の文と同義です。
000 ar & boost::serialization::make_nvp("Student", boost::serialization::base_object<Student>(*this));
基底クラスをシリアライズしつつ要素名も指定してくれます。 こちらのマクロは、ちゃんとタイプ短縮の役に立っています。
基本的には、このライブラリはロケールを設定しなくてもちゃんと動いてくれます。 が、私が調べた限りでは、1箇所だけロケールの設定を必要とする場所がありました。
001 #include <string> 002 #include <locale> 003 #include <fstream> 004 #include <boost/serialization/string.hpp> 005 #include <boost/serialization/nvp.hpp> 006 #include <boost/archive/xml_woarchive.hpp> 007 #include <boost/archive/xml_wiarchive.hpp> 008 009 int main(void) { 010 std::locale::global(std::locale("japanese")); 011 012 std::wofstream wofs("output.xml"); 013 boost::archive::xml_woarchive oa(wofs); 014 oa << boost::serialization::make_nvp("string", std::string("日本語文字列")); 015 wofs.close(); 016 017 std::string str; 018 std::wifstream wifs("output.xml"); 019 boost::archive::xml_wiarchive ia(wifs); 020 ia >> boost::serialization::make_nvp("string", str); 021 wifs.close(); 022 023 return 0; 024 }
少々長いですが、要するに「ワイド文字列アーカイブにマルチバイト文字列を流し込む」時には、 グローバルロケールがきちんと設定されている必要があるようです。
また、このケースの逆、「マルチバイト文字列アーカイブにワイド文字列を流し込む」時にもロケールの設定は必須です。