日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

      歡迎訪問 生活随笔!

      生活随笔

      當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

      编程问答

      基于泛型编程的序列化实现方法

      發(fā)布時間:2024/8/23 编程问答 26 豆豆
      生活随笔 收集整理的這篇文章主要介紹了 基于泛型编程的序列化实现方法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

      寫在前面

      序列化是一個轉(zhuǎn)儲-恢復的操作過程,即支持將一個對象轉(zhuǎn)儲到臨時緩沖或者永久文件中和恢復臨時緩沖或者永久文件中的內(nèi)容到一個對象中等操作,其目的是可以在不同的應用程序之間共享和傳輸數(shù)據(jù),以達到跨應用程序、跨語言和跨平臺的解耦,以及當應用程序在客戶現(xiàn)場發(fā)生異常或者崩潰時可以即時保存數(shù)據(jù)結(jié)構(gòu)各內(nèi)容的值到文件中,并在發(fā)回給開發(fā)者時再恢復數(shù)據(jù)結(jié)構(gòu)各內(nèi)容的值以協(xié)助分析和定位原因。

      泛型編程是一個對具有相同功能的不同類型的抽象實現(xiàn)過程,比如STL的源碼實現(xiàn),其支持在編譯期由編譯器自動推導具體類型并生成實現(xiàn)代碼,同時依據(jù)具體類型的特定性質(zhì)或者優(yōu)化需要支持使用特化或者偏特化及模板元編程等特性進行具體實現(xiàn)。

      Hello World

      #include <iostream> int main(int argc, char* argv[]) {std::cout << "Hello World!" << std::endl;return 0; }

      泛型編程其實就在我們身邊,我們經(jīng)常使用的std和stl命名空間中的函數(shù)和類很多都是泛型編程實現(xiàn)的,如上述代碼中的std::cout即是模板類std::basic_ostream的一種特化

      namespace std {typedef basic_ostream<char> ostream; }

      從C++的標準輸入輸出開始

      除了上述提到的std::cout和std::basic_ostream外,C++還提供了各種形式的輸入輸出模板類,如std::basic_istream,?std::basic_ifstream,std::basic_ofstream,?std::basic_istringstream,std::basic_ostringstream等等,其主要實現(xiàn)了內(nèi)建類型(built-in)的輸入輸出接口,比如對于Hello World可直接使用于字符串,然而對于自定義類型的輸入輸出,則需要重載實現(xiàn)操作符>>和<<,如對于下面的自定義類

      class MyClip {bool mValid;int mIn;int mOut;std::string mFilePath; };

      如使用下面的方式則會出現(xiàn)一連串的編譯錯誤

      MyClip clip; std::cout << clip;

      錯誤內(nèi)容大致都是一些clip不支持<<操作符并在嘗試將clip轉(zhuǎn)為cout支持的一系列的內(nèi)建類型如void*和int等等類型時轉(zhuǎn)換操作不支持等信息。

      為了解決編譯錯誤,我們則需要將類MyClip支持輸入輸出操作符>>和<<,類似實現(xiàn)代碼如下

      inline std::istream& operator>>(std::istream& st, MyClip& clip) {st >> clip.mValid;st >> clip.mIn >> clip.mOut;st >> clip.mFilePath;return st; } inline std::ostream& operator<<(std::ostream& st, MyClip const& clip) {st << clip.mValid << ' ';st << clip.mIn << ' ' << clip.mOut << ' ';st << clip.mFilePath << ' ';return st; }

      為了能正常訪問類對象的私有成員變量,我們還需要在自定義類型里面增加序列化和反序列化的友元函數(shù)(回憶一下這里為何必須使用友元函數(shù)而不能直接重載操作符>>和<<?),如

      friend std::istream& operator>>(std::istream& st, MyClip& clip); friend std::ostream& operator<<(std::ostream& st, MyClip const& clip);

      這種序列化的實現(xiàn)方法是非常直觀而且容易理解的,但缺陷是對于大型的項目開發(fā)中,由于自定義類型的數(shù)量較多,可能達到成千上萬個甚至更多時,對于每個類型我們則需要實現(xiàn)2個函數(shù),一個是序列化轉(zhuǎn)儲數(shù)據(jù),另一個則是反序列化恢復數(shù)據(jù),不僅僅增加了開發(fā)實現(xiàn)的代碼數(shù)量,如果后期一旦對部分類的成員變量有所修改,則需要同時修改這2個函數(shù)。

      同時考慮到更復雜的自定義類型,比如含有繼承關系和自定義類型的成員變量

      class MyVideo : public MyClip {std::list<MyFilter> mFilters; };

      上述代碼需要轉(zhuǎn)儲-恢復類MyVideo的對象內(nèi)容時,事情會變得更復雜些,因為還需要轉(zhuǎn)儲-恢復基類,同時成員變量使用了STL模板容器list與自定義類'MyFilter`的結(jié)合,這種情況也需要自己去定義轉(zhuǎn)儲-恢復的實現(xiàn)方式。

      針對以上疑問,有沒有一種方法能減少我們代碼修改的工作量,同時又易于理解和維護呢?

      Boost序列化庫

      對于使用C++標準輸入輸出的方法遇到的問題,好在Boost提供了一種良好的解決方式,則是將所有類型的轉(zhuǎn)儲-恢復操作抽象到一個函數(shù)中,易于理解,如對于上述類型,只需要將上述的2個友元函數(shù)替換為下面的一個友元函數(shù)

      template<typename Archive> friend void serialize(Archive&, MyClip&, unsigned int const);

      友元函數(shù)的實現(xiàn)類似下面的樣子

      template<typename A>void serialize(A &ar, MyClip &clip, unsigned int const ver) {ar & BOOST_SERIALIZATION_NVP(clip.mValid);ar & BOOST_SERIALIZATION_NVP(clip.mIn);ar & BOOST_SERIALIZATION_NVP(clip.mOut);ar & BOOST_SERIALIZATION_NVP(clip.mFilePath); }

      其中BOOST_SERIALIZATION_NVP是Boost內(nèi)部定義的一個宏,其主要作用是對各個變量進行打包。

      轉(zhuǎn)儲-恢復的使用則直接作用于操作符>>和<<,比如

      // store MyClip clip; ······ std::ostringstream ostr; boost::archive::text_oarchive oa(ostr); oa << clip;// load std::istringstream istr(ostr.str()); boost::archive::text_iarchive ia(istr); ia >> clip;

      這里使用的std::istringstream和std::ostringstream即是分別從字符串流中恢復數(shù)據(jù)以及將類對象的數(shù)據(jù)轉(zhuǎn)儲到字符串流中。

      對于類MyFilter和MyVideo則使用相同的方式,即分別增加一個友元模板函數(shù)serialize的實現(xiàn)即可,至于std::list模板類,boost已經(jīng)幫我們實現(xiàn)了。

      這時我們發(fā)現(xiàn),對于每一個定義的類,我們需要做的僅僅是在類內(nèi)部聲明一個友元模板函數(shù),同時類外部實現(xiàn)這個模板函數(shù)即可,對于后期類的成員變量的修改,如增加、刪除或者重命名成員變量,也僅僅是修改一個函數(shù)即可。

      Boost序列化庫已經(jīng)足夠完美了,但故事并未結(jié)束!

      在用于端上開發(fā)時,我們發(fā)現(xiàn)引用Boost序列化庫遇到了幾個挑戰(zhàn)

      • 端上的編譯資料很少,官方對端上編譯的資料基本沒有,在切換不同的版本進行編譯時經(jīng)常會遇到各種奇怪的編譯錯誤問題
      • Boost在不同的C++開發(fā)標準之間兼容性不夠好,尤其是使用libc++標準進行編譯鏈接時遇到的問題較多
      • Boost增加了端上發(fā)行包的體積
      • Boost每次序列化都會增加序列化庫及版本號等私有頭信息,反序列化時再重新解析,降低了部分場景下的使用性能

      基于泛型編程的序列化實現(xiàn)方法

      為了解決使用Boost遇到的這些問題,我們覺得有必要重新實現(xiàn)序列化庫,以剝離對Boost的依賴,同時能滿足如下要求

      • 由于現(xiàn)有工程大量使用了Boost序列化庫,因此兼容現(xiàn)有的代碼以及開發(fā)者的習慣是首要目標
      • 盡量使得代碼修改和重構(gòu)的工作量最小
      • 兼容不同的C++開發(fā)標準
      • 提供比Boost序列化庫更高的性能
      • 降低端上發(fā)行包的體積

      為了兼容現(xiàn)有使用Boost的代碼以及保持當前開發(fā)者的習慣,同時使用代碼修改的重構(gòu)的工作量最小,我們應該保留模板函數(shù)serialize,同時對于模板函數(shù)內(nèi)部的實現(xiàn),為了提高效率也不需要對各成員變量重新打包,即直接使用如下定義

      #define BOOST_SERIALIZATION_NVP(value) value

      對于轉(zhuǎn)儲-恢復的接口調(diào)用,仍然延續(xù)目前的調(diào)用方式,只是將輸入輸出類修改為

      alivc::text_oarchive oa(ostr); alivc::text_iarchive ia(istr);

      好了,到此為止,序列化庫對外的接口工作已經(jīng)做好,剩下的就是內(nèi)部的事情,應該如何重新設計和實現(xiàn)序列化庫的內(nèi)部框架才能滿足要求呢?

      先來看一下當前的設計架構(gòu)的處理流程圖

      比如對于轉(zhuǎn)儲類text_oarchive,其支持的接口必須包括

      explicit text_oarchive(std::ostream& ost, unsigned int version = 0); template <typename T> text_oarchive& operator<<(T& v); template <typename T> text_oarchive& operator&(T& v);

      開發(fā)者調(diào)用操作符函數(shù)<<時,需要首先回調(diào)到相應類型的模板函數(shù)serialize中

      template <typename T> text_oarchive& operator<<(T& v) {serialize(*this, v, mversion);return *this; }

      當開始對具體類型的各個成員進行操作時,這時需要進行判斷,如果此成員變量的類型已經(jīng)是內(nèi)建類型,則直接進行序列化,如果是自定義類型,則需要重新回調(diào)到對應類型的模板函數(shù)serialize中

      template <typename T> text_oarchive& operator&(T& v) {basic_save<T>::invoke(*this, v, mversion);return *this; }

      上述代碼中的basic_save::invoke則會在編譯期完成模板類型推導并選擇直接對內(nèi)建類型進行轉(zhuǎn)儲還是重新回調(diào)到成員變量對應類型的serialize函數(shù)繼續(xù)重復上述過程。

      由于內(nèi)建類型數(shù)量有限,因此這里我們選擇使模板類basic_save的默認行為為回調(diào)到相應類型的serialize函數(shù)中

      template <typename T, bool E = false> struct basic_load_save {template <typename A>static void invoke(A& ar, T& v, unsigned int version){serialize(ar, v, version);} };template <typename T> struct basic_save : public basic_load_save<T, std::is_enum<T>::value> { };

      這時會發(fā)現(xiàn)上述代碼的模板參數(shù)多了一個參數(shù)E,這里主要是需要對枚舉類型進行特殊處理,使用偏特化的實現(xiàn)如下

      template <typename T> struct basic_load_save<T, true> {template <typename A>static void invoke(A& ar, T& v, unsigned int version){int tmp = v;ar & tmp;v = (T)tmp;} };

      到這里我們已經(jīng)完成了重載操作符&的默認行為,即是不斷進行回溯到相應的成員變量的類型中的模板函數(shù)serialize中,但對于碰到內(nèi)建模型時,我們則需要讓這個回溯過程停止,比如對于int類型

      template <typename T> struct basic_pod_save {template <typename A>static void invoke(A& ar, T const& v, unsigned int){ar.template save(v);} };template <> struct basic_save<int> : public basic_pod_save<int> { };

      這里對于int類型,則直接轉(zhuǎn)儲整數(shù)值到輸出流中,此時text_oarchive則還需要增加一個終極轉(zhuǎn)儲函數(shù)

      template <typename T> void save(T const& v) {most << v << ' '; }

      這里我們發(fā)現(xiàn),在save成員函數(shù)中,我們已經(jīng)將具體的成員變量的值輸出到流中了。

      對于其它的內(nèi)建類型,則使用相同的方式處理,要以參考C++?std::basic_ostream的源碼實現(xiàn)。

      相應的,對于恢復操作的text_iarchive的操作流程如下圖

      測試結(jié)果

      我們對使用Boost以及重新實現(xiàn)的序列化庫進行了對比測試,其結(jié)果如下

      • 代碼修改的重構(gòu)的工作非常小,只需要刪除Boost的相關頭文件,以及將boost相關命名空間替換為alivc,BOOST_SERIALIZATION_FUNCTION以及BOOST_SERIALIZATION_NVP的宏替換
      • Android端下的發(fā)行包體積大概減少了500KB
      • 目前的消息處理框架中,處理一次消息的平均時間由100us降低到了25us
      • 代碼實現(xiàn)約300行,更輕量級

      未來還能做什么

      由于當前項目的原因,重新實現(xiàn)的序列化還沒有支持轉(zhuǎn)儲-恢復指針所指向的內(nèi)存數(shù)據(jù),但當前的設計框架已經(jīng)考慮了這種拓展性,未來會考慮支持。

      總結(jié)

      • 泛型編程能夠大幅提高開發(fā)效率,尤其是在代碼重用方面能發(fā)揮其優(yōu)勢,同時由于其類型推導及生成代碼均在編譯期完成,并不會降低性能
      • 序列化對于需要進行轉(zhuǎn)儲-恢復的解耦處理以及協(xié)助定位異常和崩潰的原因分析具有重要作用
      • 利用C++及模板自身的語言特性優(yōu)勢,結(jié)合合理的架構(gòu)設計,即易于拓展又能盡量避免過度設計


      原文鏈接
      本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

      總結(jié)

      以上是生活随笔為你收集整理的基于泛型编程的序列化实现方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

      如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。