為何需要序列化
我們常常需要將對(duì)象持久化或者在通信中傳遞對(duì)象,這時(shí)我們就會(huì)使用到序列化。
C++中的序列化
對(duì)于C++這樣的靜態(tài)語(yǔ)言來(lái)說(shuō),序列化往往都很不方便,它對(duì)于類型轉(zhuǎn)換非常敏感和嚴(yán)格,所以說(shuō)想要對(duì)C++對(duì)象進(jìn)行序列化那必須對(duì)每個(gè)序列化的對(duì)象進(jìn)行針對(duì)性編碼。
C++中的反序列化
因?yàn)镃++類型是在編譯時(shí)決定的,而序列化是運(yùn)行時(shí)行為,反序列化需要生成的對(duì)象類型必須在編譯時(shí)決定好,這讓人感到很無(wú)力。為了能夠正確的進(jìn)行反序列化,我們通常會(huì)生成一個(gè)映射表,將對(duì)象的類型所對(duì)應(yīng)的ID一并序列化,這樣就能在運(yùn)行時(shí)通過(guò)”預(yù)反序列化“得到類型ID,然后查找映射表來(lái)得到需要?jiǎng)?chuàng)建的對(duì)象類型,然后創(chuàng)建對(duì)象進(jìn)行反序列化。
序列化類型映射表
理想的序列化類型映射表能夠在編譯時(shí)生成,因?yàn)槲覀冃枰蛄谢念愋投际蔷幾g時(shí)決定好了的,所以在編譯時(shí)生成其映射表應(yīng)該理所當(dāng)然。然而非常尷尬的是,C++如何建立類型ID到類型的映射?它是靜態(tài)類型的語(yǔ)言,如果要建立一個(gè)類型的映射表,即使使用模板技術(shù)也只能做到編譯期映射(也就是需要用編譯時(shí)常量進(jìn)行類型查找,對(duì)于運(yùn)行時(shí)的反序列化行為,這顯然是沒有任何作用的)。
一個(gè)可行的方案
為了能夠在運(yùn)行時(shí)進(jìn)行查找,我使用了反序列化輔助對(duì)象作為映射類型的載體。這個(gè)對(duì)象利用模板來(lái)關(guān)聯(lián)序列化類型,它同時(shí)也要求序列化類型需要提供默認(rèn)構(gòu)造,并且默認(rèn)構(gòu)造能夠被其所訪問(wèn)。
class SerializableObject;
class UnserializerBase{
protected:
UnserializerBase(){}
public:
virtual ~UnserializerBase(){}
public:
virtual SerializableObject* Unserialize(const char*& pData) = 0;
};
template<typename T>
class Unserializer : public UnserializerBase{
public:
typedef T Object;
public:
Unserializer(){}
public:
SerializableObject* Unserialize(const char*& pData)
{
Object* pObj = new Object; // 要求需要序列化支持的類具有默認(rèn)構(gòu)造函數(shù)
pData += pObj->Unserialize(pData);
return pObj;
}
};
這里顯而易見的是我們的序列化對(duì)象必須從SerializableObject派生。
類型ID的計(jì)算
簡(jiǎn)單起見,我們可以通過(guò)C++提供的typeid來(lái)計(jì)算類型的ID,當(dāng)然這需要開啟相應(yīng)編譯開關(guān)。序列化類型可以通過(guò)模板參數(shù)將自身傳遞給某個(gè)模板函數(shù)來(lái)計(jì)算其ID,并將這個(gè)ID和反序列化輔助類型Unserializer對(duì)象建立起映射關(guān)系。
template<typename Object>
static ClassIDType BuildClassID()
{
static Unserializer<Object> unserializer;
static std::string name(typeid(Object).name());
static ClassIDType id = std::hash<BinaryData>()(BinaryData(name.c_str(), name.length()));
GetIDMapSerializer()[id] = &unserializer;
return id;
}
static std::map<ClassIDType, UnserializerBase*>& GetIDMapSerializer()
{
static std::map<ClassIDType, UnserializerBase*> id_unserializer;
return id_unserializer;
}
此處的實(shí)現(xiàn)對(duì)類型名求了hash值來(lái)作為類型ID,至于碰撞的概率和我們的序列化類型數(shù)量相比較就可以忽略不計(jì)了。并且創(chuàng)建的都是靜態(tài)對(duì)象,這能夠保證每個(gè)序列化類型值生成一次類型ID和反序列化輔助對(duì)象。
類型ID的序列化和反序列化
序列化對(duì)象對(duì)于其ID的序列化和反序列化時(shí)的查找過(guò)程并不關(guān)心,那么我們應(yīng)該透明的去完成這個(gè)過(guò)程。同時(shí)考慮到序列化對(duì)象需要將自身類型進(jìn)行注冊(cè),我們可以在此時(shí)就完成我們的類型ID到反序列化輔助對(duì)象的映射的創(chuàng)建。我們?cè)赟erializableObject基類和序列化類型之間插入了一個(gè)中間模板類,它完成我們剛才提到的映射的創(chuàng)建以及ID的序列化工作(后面講為什么它不完成ID反序列化的工作)。
// 派生類將自身類型傳遞給模板參數(shù)
template<typename T>
class Serializable : public SerializableObject{
protected:
static const ClassIDType ID;
public:
typedef T Object;
protected:
Serializable(){ID;}
public:
virtual ~Serializable(){}
public:
template<typename BufferType>
void Serialize(BufferType& buffer)
{
SaveToBuffer(ID, buffer);
// 這里應(yīng)該判斷子類序列化是否成功,如果不成功則回滾,暫時(shí)未加此功能
static_cast<Object*>(this)->DoSerialize_(buffer);
}
size_t Unserialize(const char* pData)
{
return DoUnserialize_(pData);
}
static const ClassIDType GetClassID(){return ID;}
private:
// 通過(guò)模板模擬虛函數(shù)
template<typename BufferType>
void DoSerialize_(BufferType& buffer){};
virtual size_t DoUnserialize_(const char* pData) = 0;
};
template<typename T>
const typename Serializable<T>::ClassIDType Serializable<T>::ID = Serializable<T>::BuildClassID<T>();
這里為了能夠減少對(duì)存放序列化內(nèi)容的緩沖區(qū)的依賴,我們對(duì)序列化接口使用了模板推導(dǎo),但是這導(dǎo)致我們難以通過(guò)虛函數(shù)來(lái)延遲序列化實(shí)現(xiàn)。這里我們采用了一個(gè)非標(biāo)準(zhǔn)的實(shí)現(xiàn)方式——子類注冊(cè)自身類型給父類,并要求子類必須實(shí)現(xiàn)一個(gè)名為DoSerialize_的模板成員函數(shù),那么我們就可以通過(guò)類型轉(zhuǎn)換去調(diào)用子類接口了,達(dá)到類似虛函數(shù)的功能。
我們最希望的反序列化應(yīng)該是這樣的:
Type* pObject = dynamic_cast<Type*>(Unserialize(pData));
那么,反序列化接口就不能作為一個(gè)模板類的靜態(tài)函數(shù)。所以我們將這個(gè)接口放到了SerializableObject當(dāng)中
// 此類實(shí)現(xiàn)的靜態(tài)方法Create用于反序列化
class SerializableObject{
public:
typedef size_t ClassIDType;
protected:
SerializableObject(){}
public:
virtual ~SerializableObject(){}
public:
static SerializableObject* Create(const char*& pData)
{
ClassIDType id = 0;
pData += LoadFromData(id, pData);
std::map<ClassIDType, UnserializerBase*>& id_serializer = GetIDMapSerializer();
auto iter = id_serializer.find(id);
if (id_serializer.end() != iter)
{
return (*iter).second->Unserialize(pData);
}
return 0;
}
protected:
template<typename Object>
static ClassIDType BuildClassID()
{
static Unserializer<Object> unserializer;
static std::string name(typeid(Object).name());
static ClassIDType id = std::hash<BinaryData>()(BinaryData(name.c_str(), name.length()));
GetIDMapSerializer()[id] = &unserializer;
return id;
}
private:
static std::map<ClassIDType, UnserializerBase*>& GetIDMapSerializer()
{
static std::map<ClassIDType, UnserializerBase*> id_unserializer;
return id_unserializer;
}
};
這樣,基本上我們就可以這樣來(lái)定義我們的序列化類型了:
class Test
: public Serializable<Test>
{
int num_;
public:
explicit Test(int n = 0) : num_(n){}
private:
friend class Serializable<Test>;
template<typename BufferType>
void DoSerialize_(BufferType& buffer)
{
SaveToBuffer(num_, buffer);
}
size_t DoUnserialize_(const char* pData)
{
return LoadFromData(num_, pData);
}
};
這里的SaveToBuffer和LoadFromData是一對(duì)序列化幫助函數(shù)。
這個(gè)序列化和反序列化的實(shí)現(xiàn)有個(gè)很大的缺點(diǎn),那就是映射的建立。因?yàn)槲覀兪褂玫氖悄0澹哉f(shuō)序列化類型不被使用就不會(huì)被編譯,那么這個(gè)類型就不會(huì)被加入到映射中,那么我們的程序就可能出現(xiàn)問(wèn)題(找不到某個(gè)ID所映射的反序列化輔助對(duì)象)。解決這個(gè)問(wèn)題的方法就是強(qiáng)制編譯器編譯我們可能會(huì)使用到的序列化類型,比如為每個(gè)序列化類型定義一個(gè)其類型ID的靜態(tài)引用,如果是程序中已經(jīng)使用到序列化類型的話則不必。
示例測(cè)試:
假設(shè)我們有一套命令系統(tǒng),各種不同的命令派生自一個(gè)命令基類Command,并實(shí)現(xiàn)其名為Execute的接口。我們需要將不同的命令類型對(duì)象序列化和反序列化(這在網(wǎng)絡(luò)通信協(xié)議中普遍使用,網(wǎng)絡(luò)兩端可定義相同的命令類型,一端只對(duì)這個(gè)命令對(duì)象存儲(chǔ)參數(shù),一端負(fù)責(zé)實(shí)現(xiàn)其執(zhí)行過(guò)程),反序列化過(guò)程中不需要關(guān)心具體的命令類型,只需要調(diào)用其接口Execute便可。
我們的序列化和反序列化非常適合這個(gè)系統(tǒng),下面就給出測(cè)試工程代碼,測(cè)試工程沒有建立網(wǎng)絡(luò)通信,只是體現(xiàn)我們的序列化和反序列化的方式。
下載測(cè)試工程源碼

作者: Evil.Ghost 發(fā)表于 2011-04-04 19:58 原文鏈接
評(píng)論: 0 查看評(píng)論 發(fā)表評(píng)論
最新新聞:
· 蘋果iPad 2通過(guò)3C認(rèn)證 最晚5月國(guó)內(nèi)上市(2011-04-10 09:18)
· 盲目依賴iPhone等工具導(dǎo)航 英國(guó)驢友迷路多(2011-04-10 09:14)
· 趣談:想擔(dān)任CEO的話,最好是去蘋果工作,其次是微軟,再才是Google(2011-04-10 08:26)
· 騰訊將建立新數(shù)據(jù)中心,規(guī)模為蘋果的兩倍(2011-04-10 08:25)
· 輕量化的微型博客Tumblr(2011-04-10 08:03)
編輯推薦:非戰(zhàn)之罪,從永中Office談起
網(wǎng)站導(dǎo)航:博客園首頁(yè) 我的園子 新聞 閃存 小組 博問(wèn) 知識(shí)庫(kù)