儲存 C++ 的類別資料:Boost Serialization(part 2)


前一篇 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;
  }
};

而如果是標準函式庫裡面的容器、像是 vectorlist 之類的型別,則需要加入特別的 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 來處理指標…恩,不是不行,但是感覺有很多限制。以個人來說,應該不會考慮去直接使用指標吧?如果有這部份需求的人,建議先把官方的說明認真看過一次。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.