Boost Serialization 庫教程
輸出檔案(archive)類似于輸出數據流(stream)。數據能通過<< 或 & 操作符存儲到檔案(archive)中:
ar << data;ar & data;
輸入檔案(archive)類似于輸入數據流(stream)。數據能通過>> 或 & 操作符從檔案(archive)中裝載。
ar >> data;ar & data;
對于原始數據類型,當這些操作調用的時候,數據是簡單的“被存儲/被裝載” “到/從” 檔案(archive)。對于類(class)數據類型,類的serialize 函數被調用。對上面的操作,每個serialize 函數用來“存儲/裝載”其數據成員。這個處理采用遞歸的方式,直到所有包含在類中的數據“被存儲/被裝載”。
一個非常簡單的情形
通常用serialize 函數來存儲和裝載類的數據成員。
這個庫包含一個叫 demo.cpp 的程序,用于介紹如何用這個庫。下面,我們從這個demo摘錄代碼,來介紹這個庫應用的最簡單情形。
#include <fstream> // include headers that implement a archive in simple text format#include <boost/archive/text_oarchive.hpp>#include <boost/archive/text_iarchive.hpp> /////////////////////////////////////////////////////////////// gps coordinate//// illustrates serialization for a simple type//class gps_position{private: friend class boost::serialization::access; // When the class Archive corresponds to an output archive, the // & operator is defined similar to <<. Likewise, when the class Archive // is a type of input archive the & operator is defined similar to >>. template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & degrees; ar & minutes; ar & seconds; } int degrees; int minutes; float seconds;public: gps_position(){}; gps_position(int d, int m, float s) : degrees(d), minutes(m), seconds(s) {}}; int main() { // create and open a character archive for output std::ofstream ofs("filename"); boost::archive::text_oarchive oa(ofs); // create class instance const gps_position g(35, 59, 24.567f); // write class instance to archive oa << g; // close archive ofs.close(); // ... some time later restore the class instance to its orginal state // create and open an archive for input std::ifstream ifs("filename", std::ios::binary); boost::archive::text_iarchive ia(ifs); // read class state from archive gps_position newg; ia >> newg; // close archive ifs.close(); return 0;}對于每個通過序列化“被存儲”的類,必須存在一個函數去實現“存儲”其所有狀態數據。對于每個通過序列化“被裝載”的類,必須存在一個函數來實現“裝載”其所有狀態數據。在上面的例子中,這些函數是模板成員函數serialize。
非侵入的版本
在上例是侵入的設計。類是需要由其實例來序列化,來改變。這在某些情形是困難的。一個等價的可選的設計如下:
#include <boost/archive/text_oarchive.hpp>#include <boost/archive/text_iarchive.hpp> class gps_position{public: int degrees; int minutes; float seconds; gps_position(){}; gps_position(int d, int m, float s) : degrees(d), minutes(m), seconds(s) {}}; namespace boost {namespace serialization { template<class Archive>void serialize(Archive & ar, gps_position & g, const unsigned int version){ ar & g.degrees; ar & g.minutes; ar & g.seconds;} } // namespace serialization} // namespace boost
這種情況生成的serialize 函數不是gps_position類的成員函數。這有異曲同工之妙。
非侵入序列化主要應用在不改變類定義就可實現類的序列化。為實現這種可能,類必須提供足夠的信息來更新類狀態。在這個例子中,我們假設類有public成員。僅當提供足夠信息來存儲和裝載的類,才能不改變類自身,在外部來序列化類狀態。
可序列化的成員
一個可序列化的類,可擁有可序列化的成員,例如:
class bus_stop{ friend class boost::serialization::access; template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & latitude; ar & longitude; } gps_position latitude; gps_position longitude;protected: bus_stop(const gps_position & lat_, const gps_position & long_) : latitude(lat_), longitude(long_) {}public: bus_stop(){} // See item # 14 in Effective C++ by Scott Meyers. // re non-virtual destructors in base classes. virtual ~bus_stop(){}};
這里,類類型的成員被序列化,恰如原始類型被序列化一樣。
注意,類bus_stop的實例“存儲”時,其歸檔(archive)操作符將調用latitude 和 longitude的serialize 函數。這將依次調用定義在gps_position中的serialize 來被“存儲”。這種手法中,通過bus_stop的歸檔(archive)操作符,整個數據結構被存儲,bus_stop是它的根條目。
派生類
派生類應包含其基類的序列化。
#include <boost/serialization/base_object.hpp> class bus_stop_corner : public bus_stop{ friend class boost::serialization::access; template<class Archive> void serialize(Archive & ar, const unsigned int version) { // serialize base class information ar & boost::serialization::base_object<bus_stop>(*this); ar & street1; ar & street2; } std::string street1; std::string street2; virtual std::string description() const { return street1 + " and " + street2; }public: bus_stop_corner(){} bus_stop_corner(const gps_position & lat_, const gps_position & long_, const std::string & s1_, const std::string & s2_ ) : bus_stop(lat_, long_), street1(s1_), street2(s2_) {}};注意在派生類中不要直接調用其基類的序列化函數。這樣做看似工作,實際上繞過跟蹤實例用于存儲來消除冗余的代碼。它也繞過寫到檔案中類的版本信息的代碼。因此,總是聲明serialize 作為私有函數。聲明friend boost::serialization::access 將運行序列化庫存取私有變量和函數。
指針
假設我們定義了bus route包含一組bus stops。假定:
- 我們可以有幾種bus stop的類型(記住bus_stop是一個基類)。
- 一個所給的 bus_stop可以展現多于一個的路線。
一個bus route 用一組指向bus_stop的指針來描述是方便的。
class bus_route{ friend class boost::serialization::access; bus_stop * stops[10]; template<class Archive> void serialize(Archive & ar, const unsigned int version) { int i; for(i = 0; i < 10; ++i) ar & stops[i]; }public: bus_route(){}};數組stops 的每個成員將被序列化。但是,記住每個成員是個指針。 - 實際含義是什么?序列化整個對象是要求在另一個地方和時間重新構造原始數據結構。用指針為了完成這些,存儲指針的值是不夠的,指針指向的對象必須存儲。當成員最后被裝載,一個新的對象被創建,新的指針被裝載到類的成員中。
所有這一切是由序列化庫自動完成的。通過指針關聯的對象,上述代碼能完成存儲和裝載。
數組
事實上上述方案比較復雜。序列化庫能檢測出被序列化的對象是一個數組,將產生上述等價的代碼。因此上述代碼能更短的寫為:
class bus_route{ friend class boost::serialization::access; bus_stop * stops[10]; template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & stops; }public: bus_route(){}};STL容器
上面的例子用數組成員。更多的如此的一個應用用STL容器為如此的目的。序列化庫包含為所有STL容器序列化的代碼。因此,下種方案正如我們所預期的樣子工作。
#include <boost/serialization/list.hpp> class bus_route{ friend class boost::serialization::access; std::list<bus_stop *> stops; template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & stops; }public: bus_route(){}};類的版本
假設我們對bus_route類滿意,在產品中使用它。一段時間后,發覺bus_route 類需要包含線路駕駛員的名字。因此新版本如下:
#include <boost/serialization/list.hpp>#include <boost/serialization/string.hpp> class bus_route{ friend class boost::serialization::access; std::list<bus_stop *> stops; std::string driver_name; template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & driver_name; ar & stops; }public: bus_route(){}};好,完畢!異常...會發生在讀取舊版本所生成的數據文件時。如何考慮版本問題?
通常,序列化庫為每個被序列化的類在檔案中存儲版本號。缺省值是0。當檔案裝載時,存儲的版本號可被讀出。上述代碼可修改如下:
#include <boost/serialization/list.hpp>#include <boost/serialization/string.hpp>#include <boost/serialization/version.hpp> class bus_route{ friend class boost::serialization::access; std::list<bus_stop *> stops; std::string driver_name; template<class Archive> void serialize(Archive & ar, const unsigned int version) { // only save/load driver_name for newer archives if(version > 0) ar & driver_name; ar & stops; }public: bus_route(){}}; BOOST_CLASS_VERSION(bus_route, 1)對每個類通過應用的版本,沒有必要維護一個版本文件。一個文件版本是所有它組成的類的版本的聯合。系統允許程序和以前版本的程序創建的檔案向下兼容。
把serialize拆分成save/load
serialize函數是簡單,簡潔,并且保證類成員按同樣的順序(序列化系統的key)被存儲/被裝載。可是有像這里例子一樣,裝載和存儲不一致的情形。例如,一個類有多個版本的情況發生。上述情形能重寫為:
#include <boost/serialization/list.hpp>#include <boost/serialization/string.hpp>#include <boost/serialization/version.hpp>#include <boost/serialization/split_member.hpp> class bus_route{ friend class boost::serialization::access; std::list<bus_stop *> stops; std::string driver_name; template<class Archive> void save(Archive & ar, const unsigned int version) const { // note, version is always the latest when saving ar & driver_name; ar & stops; } template<class Archive> void load(Archive & ar, const unsigned int version) { if(version > 0) ar & driver_name; ar & stops; } BOOST_SERIALIZATION_SPLIT_MEMBER()public: bus_route(){}}; BOOST_CLASS_VERSION(bus_route, 1)BOOST_SERIALIZATION_SPLIT_MEMBER() 宏生成調用 save 或 load的代碼,依賴于是否檔案被用于“存儲”或“裝載”。
檔案
我們這里討論將聚焦到類的序列化能力上。被序列化的數據的實際編碼實現于檔案(archive)類中。被序列化的數據流是所選檔案(archive)類的序列化的產物。(鍵)key設計決定這兩個組件的獨立性。允許任何序列化的規范可用于任何檔案(archive)。
在這篇指南中,我們用了一個檔案類-用于存儲的text_oarchive和用于裝載的text_iarchive類。在庫中其他檔案類的接口完全一致。一旦類的序列化已經被定義,類能被序列化到任何檔案類型。
假如當前的檔案集不能提供某個屬性,格式,或行為需要特化的應用。要么創建一個新的要么從已有的里面衍生一個。將在后繼文檔中描述。
注意我們的例子save和load程序數據在一個程序中,這是為了討論方便而已。通常,被裝載的檔案或許在或許不在同一個程序中。
T完整的演示程序 - demo.cpp 包括:
- 創建各種類別的 stops, routes 和 schedules
- 顯示它
- 序列化到一個名叫 "testfile.txt"的文件中
- 還原到另一個結構中
- 顯示被存儲的結構
這個程序的輸出 分證實了對序列化系統所有的要求,都在這個系統中體現了。對序列化文件是ASCII文本的檔案文件的內容 能被顯示。
posted on 2008-10-19 00:38 肥仔 閱讀(2675) 評論(1) 編輯 收藏 引用 所屬分類: Boost & STL

