前一篇 part 1 大致上把 Boost serialization 的基本使用整理了一下。接下來這篇,就繼續整理一下其他的東西吧~
將 serialize() 改成 private
首先,在之前提到的例子裡面,如果是選擇在類別中定義成員函式 serialize() 的時候, 是直接把它定義成 public 的。
但是考慮到這個函式的特殊性,或許他並不適合做為一個外部可以直接呼叫的 public 函式。
所以這邊比較好的做法,應該是將它改為 private、並透過設定 friend class、來讓它可以被 Boost serialization 提供的 archive 呼叫,理論上會比較安全。
前一篇的 CURL 的例子,就可以改成:
class CURL { public: std::string sHost; unsigned int sPort; std::string sPath; private: friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar& sHost; ar& sPort; ar& sPath; } };
如此一來,這個函式就比較不會被誤用了。
繼承的類別
如果再遇到類別有繼承的時候,官方是有說「不要」直接去呼叫 base class 的 serialize() 函式;雖然可以好像可以用,但是實際上卻可能會跳過一些內部的機制。
所以在這個時候,官方的建議是,把永遠把 serialize() 這個成員函式寫成 private 的,然後透過 boost::serialization::base_object<>() 來處理 base class 的資料。
下面就是一個繼承上面的 CURL 的例子:
class CAuth : public CURL { public: std::string sAccount; std::string sPassword; private: friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar& boost::serialization::base_object<CURL>(*this); ar& sAccount; ar& sPassword; } };
陣列、其他 STL 型別
在原生矩陣(Array)的部分,其實 Boost serialization 是可以直接支援的。
下面面例子裡面的 aSite 這個 CURL 的陣列,就可以直接給 archive 用。
class CurlList { public: CURL aSite[3]; private: friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar& aSite; } };
而如果是標準函式庫裡面的容器、像是 vector、list 之類的型別,則需要加入特別的 header 才能使用。
比如說把上面的 CURL aSite[3]; 改成 std::vector<CURL> vSite; 的話,在要序列化之前,就需要 include <boost/serialization/vector.hpp> 這個檔案,否則會出現沒有serialize() 這個成員函式的錯誤。
而在 boost/serialization/ 這個資料夾下,也還有很多其他型別用的 header 檔,如果遇到不能序列化的型別,也可以先來這邊看看有沒有對應的檔案。
至於其他 Boost 函式庫的類別,有的則是藏在各自的目錄下,有需要可能也得自己挖看看了。例如 boost UUID 的序列化程式就是 boost/uuid/uuid_serialize.hpp 這個檔案。
類別的版本
另外,前面也有提到,serialize() 這個函式的最後一個參數,是用來作為版本控管用的。而在 Boost serialization 的設計,什麼都不指定的話,預設的版本就是 0。
如果想要設定版本的話,則可以透過 BOOST_CLASS_VERSION 這個巨集、來設定類別的版本編號。
要使用的話,基本上就是:
BOOST_CLASS_VERSION(CURL, 1);
這樣的形式了。
而在 serialize() 中,就可以根據得到的版本編號,來做不同的控制。
另外,這個巨集是被定義在 <boost/serialization/version.hpp> 這個 header 裡,可能會需要 include。
將儲存和讀取分開處理
根據 Boost serialization 的設計下,主要是透過 serialize() 這樣的單一函式,來解決輸入、輸出的功能,並避免可能的兩個方向不一致的可能性。
但是,有的時候,我們可能還是會需要輸入、輸出兩邊是不一樣的狀況。
例如,有的函式庫的類別其實會把成員資料都設定成 private 或 proteced、僅能透過成員函式做存取,以保護資料。
比如說,可能會像下面的樣子:
class CURL { protected: std::string sHost; unsigned int sPort; std::string sPath; public: std::string getHost() const { return sHost; } std::string getPath() const { return sPath; } unsigned int getPort() const { return sPort; } void setHost(const std::string& val) { sHost = val; } void setPath(const std::string& val) { sPath = val; } void setPort(const unsigned int& val) { sPort = val; } };
這個時候,除非要去直接修改 CURL 這個類別,基本上就沒辦法靠單一的 serialize() 來同時處理輸出和輸入。
如果遇到這樣的情況,Boost serialization 也提供了把 serialize() 拆分成 save() / load() 兩個函式的功能,讓開發者可以使用。
比如說,這邊就可以寫成:
namespace boost { namespace serialization { template<class Archive> void save(Archive& ar, const CURL& t, unsigned int version) { ar& t.getHost(); ar& t.getPort(); ar& t.getPath(); } template<class Archive> void load(Archive& ar, CURL& t, unsigned int version) { std::string sVal; unsigned int iVal; ar& sVal; t.setHost(sVal); ar& iVal; t.setPort(iVal); ar& sVal; t.setPath(sVal); } } } BOOST_SERIALIZATION_SPLIT_FREE(CURL);
這邊的作法,基本上就是和撰寫 serialize() 的方法依樣,不過這邊把程式分成 save() / load() 兩個版本。
最後,則是要再透過 BOOST_SERIALIZATION_SPLIT_FREE() 這個巨集(定義在 <boost/serialization/split_free.hpp>),告訴 Boost Serialization CURL 這個類別需要使用拆開的版本,然後就可以正常使用了~
不過,如果是要這樣寫的話,就真的得自己注意兩個函式內容的一致性了~
而如果是成員函式的 serialize() 要拆分的話,則是要使用 BOOST_SERIALIZATION_SPLIT_MEMBER() 這個巨集(定義在 <boost/serialization/split_member.hpp>)。
Boost Serialization 的整理大概就先這樣了。理論上這根據這些內容,應該已經算是夠用了?
而最後,如果要直接透過 Boost Serialization 來處理指標…恩,不是不行,但是感覺有很多限制。以個人來說,應該不會考慮去直接使用指標吧?如果有這部份需求的人,建議先把官方的說明認真看過一次。