1. 程式人生 > >C++序列化與反序列化的簡單探索

C++序列化與反序列化的簡單探索

序列化是指將資料從有結構清晰的語言定義的資料形式轉化為二進位制字串,反序列化則是序列化的逆操作。

百度百科定義序列化如下:

序列化 (Serialization)將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程。在序列化期間,物件將其當前狀態寫入到臨時或永續性儲存區。以後,可以通過從儲存區中讀取或反序列化物件的狀態,重新建立該物件。

關於序列化,其實在MapBox中就已經有雛形。MapBox的作法是使用map<string,string>作為底層容器,第一元素為 Tag標籤,第二元素為 Context內容。方法toString()能夠將MapBox轉化為一個字串,這個過程中MapBox是使用base64編碼+特殊字元實現的。MapBox沒有提供反序列化方法,只是在通過網路接收資料的時候能夠 將字串解析為MapBox。(畢竟MapBox的設計初衷就是網路程式設計,起初打算打造一個MapBox over socket的東西...,但是發現base64本身存在的缺點在資料量暴漲的時候大於base64的優點,於是放棄了MapBox over socket的設計。但是現在MapBox在傳輸不超過200MB的內容時仍然具有較好的表現)

最近翻了翻MapBox,序列化與反序列化的想法又冒了出來。Protocol Buffer的話,需要呼叫谷歌自己的編譯程式(把rule編譯為header),感覺比較麻煩(不過比其他的工具簡單不少了)。Boost庫的話,序列化倒是很方便,但是看了原始碼之後發現各種模板暫時理解不能... 所以還是想自己寫一個小東西試一試。

/** Templates */
template<typename T>
string serialize(const T& a)
{
    return T::serialize(a);
}

template<typename T>
int deserialize(string str,T& a)
{
    return T::deserialize(str,a);
}
這裡是序列化以及反序列化的通用方法。其實就是呼叫了一下T型別的序列化和反序列化函式。
/** Special Version
* For...
*   int, double, float
*/
/// int
template<>
string serialize(const int& a)
{
    string ans;
    int c=htonl(a);
    ans.append((const char*)&c,sizeof(c));
    return ans;
}
template<>
int deserialize(string str,int& c)
{
    memcpy(&c,str.data(),sizeof(c));
    c=ntohl(c);
    return sizeof(c);
}
上面是針對int的特化,進行了位元組序的轉換
/// string
template<>
string serialize(const string& a)
{
    int len=a.size();
    string ans;
    ans.append(::serialize(len));
    ans.append(a);
    return ans;
}
template<>
int deserialize(string str,string& a)
{
    int len;
    ::deserialize(str,len);
    a=str.substr(sizeof(len),len);
    return sizeof(int)+len;
}
以上是針對string的特化,在序列化最前面加入了長度。之前沒有這一個長度,結果string和string相連的時候出現了比較棘手的問題......
/// Marco definition
#define NORMAL_DATA_SERIALIZE(Type) template<> \
    string serialize(const Type& a) \
    { \
        string ans; \
        ans.append((const char*)&a,sizeof(a)); \
        return ans; \
    }
#define NORMAL_DATA_DESERIALIZE(Type) template<> \
    int deserialize(string str,Type& a)\
    { \
        memcpy(&a,str.data(),sizeof(a)); \
        return sizeof(a); \
    }

針對POD而且不需要位元組序轉換的巨集定義
/// double
NORMAL_DATA_SERIALIZE(double);
NORMAL_DATA_DESERIALIZE(double);
NORMAL_DATA_SERIALIZE(float);
NORMAL_DATA_DESERIALIZE(float);
NORMAL_DATA_SERIALIZE(char);
NORMAL_DATA_DESERIALIZE(char); 
double與char,視為POD+無位元組序問題的型別
template<typename SerializableType>
class Serializable
{
public:
    static SerializableType deserialize(string);
    static string serialize(const SerializableType& a);
};
可序列化模板類,如果某個型別希望能夠序列化,那麼應該繼承這個類並實現其中的兩個方法。
class OutEngine
{
public:
    template<typename SerializableType>
    OutEngine& operator << (SerializableType& a)
    {
        string x=::serialize(a);
        os.write(x.data(),x.size());
        return *this;
    }
    string str()
    {
        return os.str();
    }
    void set_empty()
    {
        os.str("");
    }
    OutEngine():os(std::ios::binary){}
public:
    ostringstream os;
};
一個輸出引擎,使用方法大概就是宣告 OutEngine oe;int a=3;然後oe<<a; 呼叫oe.str()來獲取序列化字串
class InEngine
{
public:
    InEngine(string s) : is(s){n_size=leftsize();}
    template<typename SerializableType>
    InEngine& operator >> (SerializableType& a)
    {
        int ret=::deserialize(is,a);
        is=is.substr(ret);
        return *this;
    }
    void set_str(string s)
    {
        is=s;
        n_size=leftsize();
    }

    int leftsize()
    {
        return is.size();
    }
    int donesize()
    {
        return n_size-leftsize();
    }

protected:
    string is;
    int n_size;
};
一個輸入引擎,原來使用istringstream,後來就直接換成了string,配合substr


測試程式碼如下

注: 三個成員設計為public是因為方便main裡面的程式碼

class cbox : public Serializable<cbox>
{
public:
    int a;
    double b;
    string str;

    static string serialize(const cbox& inc)
    {
        OutEngine x;
        x<<inc.a<<inc.b<<inc.str;
        return x.str();
    }
    static int deserialize(string inc,cbox& box)
    {
        InEngine x(inc);
        x>>box.a>>box.b>>box.str;
        return x.donesize();
    }
};
int main()
{
    cbox box;
    box.a=11;
    box.b=6.6;
    box.str="Hello World";

    cbox box3;
    box3.a=33;
    box3.b=12.5;
    box3.str="Yummy Hamburger!";

    OutEngine oe;
    oe<<box<<box3;

    string b=oe.str();
    cout<<b<<endl;

    cbox box2;
    cbox box4;
    InEngine ie(b);
    ie>>box2>>box4;

    cout<<box2.a<<endl;
    cout<<box2.b<<endl;
    cout<<box2.str<<endl;

    cout<<box4.a<<endl;
    cout<<box4.b<<endl;
    cout<<box4.str<<endl;
    return 0;
}

輸出內容(二進位制字串在不同裝置上的輸出應該是不同的,但是內容應該是一致的)