需要使用到序列化場景的需求
- 在寫代碼的過程中,經常會需要把代碼層面的對象數據保存到文件,而這些數據會以各種格式存儲.例如:json,xml,二進制等等.二進制,相比json,xml格式,直接把字節copy到硬盤,所以這實現起來相對容易.
例子
-
下面是一個簡單的三維向量結構體,如何把它序列化到文件呢?
struct Vec3 {float x;float y;float z;
}
Vec3 v;
v.x = 1.0f;
v.y = 2.0f;
v.z = 3.0f;
os.write((const char *)&v, sizeof(Vec3));
- 上述是序列化Vec3對象數據到文件的代碼,非常直接.它的內存布局是3個浮點型變量緊湊排列,要把它存儲到硬盤,只要從頭到尾按字節拷貝即可.但是,在實際開發中,要序列化的對象不可能全部都是內存緊湊排列的,例如STL容器.
std::vector<Vec3> vec;
- 如果將容器變量從頭到尾拷貝到文件,必然會出現錯誤.因為容器內部通過一個指針來訪問存儲的對象,而直接拷貝這個容器,只會把指針拷貝,指針指向的數據卻丟失了.但是,容器提供了一個可以直接訪問指針指向數據的接口,可以通過這個接口得到數據然后直接拷貝.
os.write((const char *)&vec, vec.size() * sizeof(Vec3)); // 錯誤, 僅拷貝指針
os.write((const char *)vec.data(), vec.size() * sizeof(Vec3)); // 正確, 數據被完全拷貝
- 通過這個方法就可以得到正確的拷貝結果了.通常,好的做法是將序列化和反序列化封裝成接口,以便于使用,如何封裝接口,就是這篇文章的主題.從上述兩個例子可以發現,對于單體對象和數組對象,編寫的代碼是不一樣的,單體對象直接拷貝,數組對象需要通過?
.data()
?取得數據地址再進行拷貝.而考慮到還有嵌套數組像?std::vector<std::vector<Vec3>>
.對于嵌套數組序列化的代碼可能如下:
std::vector<std::vector<Vec3>> vec2;
for (auto & vec: vec2)
{os.write((const char *)vec.data(), vec.size() * sizeof(Vec3));
}
- 可以發現,對嵌套數組對象的序列化代碼跟上述2種對象又不一樣,考慮到還有N層嵌套的數組對象.此外,在C++中有一個?可平凡復制的概念?,通俗的說,就是可以直接按字節拷貝的結構稱之為?可平凡復制?,上述的?
Vec3
?則是一個可平凡復制結構,而STL容器則不是可平凡復制結構,除此之外還有更多不可平凡復制且非容器的結構,故此,如果要封裝接口,除了區分單體對象和數組對象,還要區分可平凡復制和不可平凡復制.
void Serialize(std::ostream & os, const Type & val); // 序列化
void Deserialize(std::istream & is, Type & val); // 反序列化
// POD
template <class T, typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> N = 0>
void Serialize(std::ostream & os, const T & val)
{os.write((const char *)&val, sizeof(T));
}// 容器
template <class T, typename std::enable_if_t<std::is_same_v<typename T::iterator, decltype(std::declval<T>().begin())> &&std::is_same_v<typename T::iterator, decltype(std::declval<T>().end())>, int> N = 0>void Serialize(std::ostream & os, const T & val)
{unsigned int size = val.size();os.write((const char *)&size, sizeof(size));for (auto & v : val) { Serialize(os, v); }
}// POD
template <class T, typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> N = 0>
void Deserialize(std::istream & is, T & val)
{is.read((char *)&val, sizeof(T));
}// 容器
template <class T, typename std::enable_if_t<std::is_same_v<typename T::iterator, decltype(std::declval<T>().begin())> &&std::is_same_v<typename T::iterator, decltype(std::declval<T>().end())>, int> N = 0>void Deserialize(std::istream & is, T & val)
{unsigned int size = 0;is.read((char *)&size, sizeof(unsigned int));val.resize(size);for (auto & v : val) { Deserialize(is, v); }
}
- 以上實現可序列化任意?可平凡拷貝?結構,并且也可序列化任意嵌套層數的STL風格數組.而對于?不可平凡復制?結構,只需要針對該結構重載即可.借助C++強大的類型推導機制和SFINEA機制,可保證類型安全又具備可擴展性.