使用 C++ 进行高效序列化和反序列化的实现(优化版本)
文章目录
- 0. 引言
- 1. 设计思路
- 2. 代码实现
- 2.1 `serialize.hpp`头文件
- 2.2 代码解析
- 3. 测试程序
- 4. 测试结果
- 5. 优缺点分析
- 5.1 优点
- 5.2 缺点
0. 引言
之前的文章 二进制序列化与反序列化:支持C++基础类与STL容器已介绍了serialize.hpp,本文将给一个支持C++14以上版本的优化版本。
1. 设计思路
使用模板和 SFINAE(Substitution Failure Is Not An Error)机制,以支持多种数据类型。通过重载序列化和反序列化函数,处理不同的数据类型。
2. 代码实现
2.1 serialize.hpp
头文件
#ifndef SERIALIZE_HPP
#define SERIALIZE_HPP#include <algorithm>
#include <cstdint>
#include <iterator>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>namespace Serialization {// 用于判断是否可以平凡复制
template <typename T>
constexpr bool IsTriviallyCopyable = std::is_trivially_copyable<T>::value;// 序列化
template <typename T>
void serialize(std::ostream &os, const T &val, typename std::enable_if<IsTriviallyCopyable<T>, int>::type = 0) {os.write(reinterpret_cast<const char *>(&val), sizeof(T));
}// 处理 std::pair
template <typename K, typename V>
void serialize(std::ostream &os, const std::pair<K, V> &val) {serialize(os, val.first);serialize(os, val.second);
}// 处理 std::string
void serialize(std::ostream &os, const std::string &val) {const std::size_t size = val.size();os.write(reinterpret_cast<const char *>(&size), sizeof(size));os.write(val.data(), size); // 注意这里的数据类型匹配
}// 序列化容器
template <typename Container>
void serialize(std::ostream &os, const Container &container,typename std::enable_if<std::is_same<typename std::iterator_traits<typename Container::iterator>::value_type,typename Container::value_type>::value,int>::type = 0) {const std::size_t size = container.size();os.write(reinterpret_cast<const char *>(&size), sizeof(size));for (const auto &item : container) {serialize(os, item);}
}// 序列化元组
template <typename Tuple, std::size_t... Indices>
void serializeTuple(std::ostream &os, const Tuple &tup, std::index_sequence<Indices...>) {// 使用循环替代折叠表达式int dummy[] = {(serialize(os, std::get<Indices>(tup)), 0)...}; // 创建一个无用数组来展开索引static_cast<void>(dummy); // 消除未使用变量的警告
}template <typename... Args>
void serialize(std::ostream &os, const std::tuple<Args...> &val) {serializeTuple(os, val, std::index_sequence_for<Args...>{});
}// 反序列化
template <typename T>
void deserialize(std::istream &is, T &val, typename std::enable_if<IsTriviallyCopyable<T>, int>::type = 0) {is.read(reinterpret_cast<char *>(&val), sizeof(T));
}// 处理 std::string
void deserialize(std::istream &is, std::string &val) {std::size_t size = 0;is.read(reinterpret_cast<char *>(&size), sizeof(size));val.resize(size);is.read(&val[0], size); // 使用 &val[0] 以获得 char* 类型
}// 反序列化容器
template <typename Container>
void deserialize(std::istream &is, Container &container,typename std::enable_if<std::is_same<typename std::iterator_traits<typename Container::iterator>::value_type,typename Container::value_type>::value,int>::type = 0) {std::size_t size = 0;is.read(reinterpret_cast<char *>(&size), sizeof(size));container.clear();container.reserve(size);for (std::size_t i = 0; i < size; ++i) {typename Container::value_type item;deserialize(is, item);container.emplace_back(std::move(item));}
}// 反序列化元组
template <typename Tuple, std::size_t... Indices>
void DeserializeTuple(std::istream &is, Tuple &tup, std::index_sequence<Indices...>) {// 使用循环替代折叠表达式int dummy[] = {(deserialize(is, std::get<Indices>(tup)), 0)...}; // 创建一个无用数组来展开索引static_cast<void>(dummy); // 消除未使用变量的警告
}template <typename... Args>
void deserialize(std::istream &is, std::tuple<Args...> &val) {DeserializeTuple(is, val, std::index_sequence_for<Args...>{});
}} // namespace Serialization#endif // SERIALIZE_HPP
2.2 代码解析
-
平凡复制检测:使用
std::is_trivially_copyable
检查类型是否可以平凡复制,以决定是否使用简单的内存拷贝。 -
序列化函数:重载多个
serialize
函数以支持基本类型、std::string
、容器(如std::vector
和std::map
)及std::tuple
。元组的序列化使用了索引序列来展开参数。 -
反序列化函数:类似于序列化,定义了多个
deserialize
函数以支持不同的数据类型和容器。
3. 测试程序
#include <chrono>
#include <iostream>
#include <list>
#include <map>
#include <sstream>
#include <tuple>
#include <unordered_map>
#include <vector>#include "serialize.hpp"struct Rect_t {uint32_t x = 0;uint32_t y = 0;uint32_t w = 0;uint32_t h = 0;bool operator==(const Rect_t &other) const {return x == other.x && y == other.y && w == other.w && h == other.h;}
};struct BBox_t {std::vector<Rect_t> rects;uint32_t index = 0;bool operator==(const BBox_t &other) const {return rects == other.rects && index == other.index;}
};// 测试函数
void runPerformanceTest(size_t numBoxes, size_t numRectsPerBox) {std::vector<BBox_t> bboxs(numBoxes);// 初始化数据for (size_t j = 0; j < numBoxes; ++j) {BBox_t &bbox = bboxs[j];bbox.rects.reserve(numRectsPerBox);for (size_t i = 0; i < numRectsPerBox; ++i) {Rect_t rect;rect.x = static_cast<uint32_t>(i + 100);rect.y = static_cast<uint32_t>(i + 200);rect.w = static_cast<uint32_t>(i + 300);rect.h = static_cast<uint32_t>(i + 400);bbox.rects.push_back(rect);}bbox.index = static_cast<uint32_t>(j);}// 序列化性能测试std::ostringstream ss;auto startSerialize = std::chrono::high_resolution_clock::now();for (const auto &bbox : bboxs) {Serialization::serialize(ss, bbox.rects);Serialization::serialize(ss, bbox.index);}auto endSerialize = std::chrono::high_resolution_clock::now();std::chrono::duration<double, std::milli> serializeDuration = endSerialize - startSerialize;std::cout << "Serialization Time: " << serializeDuration.count() << " ms" << std::endl;// 反序列化性能测试std::istringstream is(ss.str());auto startDeserialize = std::chrono::high_resolution_clock::now();std::vector<BBox_t> deserializedBboxes;while (!is.eof()) {BBox_t bbox;Serialization::deserialize(is, bbox.rects);Serialization::deserialize(is, bbox.index);deserializedBboxes.push_back(bbox);}auto endDeserialize = std::chrono::high_resolution_clock::now();std::chrono::duration<double, std::milli> deserializeDuration = endDeserialize - startDeserialize;std::cout << "Deserialization Time: " << deserializeDuration.count() << " ms" << std::endl;// 检查序列化和反序列化结果的一致性bool consistent = true;for (size_t i = 0; i < bboxs.size(); ++i) {if (!(bboxs[i] == deserializedBboxes[i])) {consistent = false;break;}}if (consistent) {std::cout << "Serialization and deserialization were successful and consistent!" << std::endl;} else {std::cout << "Mismatch between original and deserialized data!" << std::endl;}// 测试 std::tupleusing TupleType = std::tuple<int, float, std::string>;std::vector<TupleType> tuples = {{1, 1.1f, "one"},{2, 2.2f, "two"},{3, 3.3f, "three"},};// 序列化 std::tuplestd::ostringstream tupleStream;for (const auto &tuple : tuples) {Serialization::serialize(tupleStream, tuple);}// 反序列化 std::tuplestd::istringstream tupleIs(tupleStream.str());std::vector<TupleType> deserializedTuples;while (!tupleIs.eof()) {TupleType tuple;Serialization::deserialize(tupleIs, tuple);deserializedTuples.push_back(tuple);}// 检查 std::tuple 的一致性consistent = true;for (size_t i = 0; i < tuples.size(); ++i) {if (tuples[i] != deserializedTuples[i]) {consistent = false;break;}}if (consistent) {std::cout << "Tuple serialization and deserialization were successful and consistent!" << std::endl;} else {std::cout << "Mismatch between original and deserialized tuples!" << std::endl;}// 测试其他容器类型std::map<int, Rect_t> rectMap;for (size_t i = 0; i < numRectsPerBox; ++i) {rectMap[i] = {static_cast<uint32_t>(i + 100), static_cast<uint32_t>(i + 200), static_cast<uint32_t>(i + 300),static_cast<uint32_t>(i + 400)};}std::ostringstream mapStream;Serialization::serialize(mapStream, rectMap);std::unordered_map<int, Rect_t> rectUnorderedMap;for (size_t i = 0; i < numRectsPerBox; ++i) {rectUnorderedMap[i] = {static_cast<uint32_t>(i + 100), static_cast<uint32_t>(i + 200),static_cast<uint32_t>(i + 300), static_cast<uint32_t>(i + 400)};}std::ostringstream unorderedMapStream;Serialization::serialize(unorderedMapStream, rectUnorderedMap);
}int main() {const size_t numBoxes = 10000;const size_t numRectsPerBox = 2;runPerformanceTest(numBoxes, numRectsPerBox);return 0;
}
4. 测试结果
test@t:~/performance_test$ g++ -std=c++14 performance_test.cpp -o performance_test -O2
test@t:~/performance_test$ ./performance_test
Serialization Time: 2.94966 ms
Deserialization Time: 4.25228 ms
Serialization and deserialization were successful and consistent!
Tuple serialization and deserialization were successful and consistent!
test@t:~/performance_test$ ./performance_test
Serialization Time: 2.99065 ms
Deserialization Time: 4.30422 ms
Serialization and deserialization were successful and consistent!
Tuple serialization and deserialization were successful and consistent!
5. 优缺点分析
以下是这个 C++ 序列化库的优缺点:
5.1 优点
-
简单易用:
- 提供简单的 API,易于序列化和反序列化多种数据类型,包括基本类型、容器和元组。
-
高性能:
- 采用直接内存读写,支持平凡复制的类型,提供较高的序列化和反序列化速度。
-
类型安全:
- 使用模板和类型检查,确保只有支持的类型能够被序列化和反序列化,从而减少运行时错误。
-
可扩展性:
- 可以为自定义类型提供特定的序列化和反序列化逻辑,只需重载相关函数。
-
支持多种容器:
- 内置对标准库容器(如
std::vector
、std::map
等)的支持,方便处理常见数据结构。
- 内置对标准库容器(如
5.2 缺点
-
缺乏跨平台兼容性:
- 使用原始字节序列可能在不同平台之间产生兼容性问题,尤其是在字节顺序(endianness)和数据对齐方面。
-
不支持复杂类型:
- 对于复杂类型(如包含指针、动态分配内存等的类型),需要用户自行实现序列化和反序列化逻辑。
-
错误处理不足:
- 当前实现缺乏对序列化和反序列化过程中的错误处理,可能导致数据损坏或未定义行为。
-
内存管理:
- 在处理大型数据时,使用
std::ostringstream
和std::istringstream
可能会引入额外的内存开销。
- 在处理大型数据时,使用
-
功能有限:
- 与成熟的序列化库(如 Google Protobuf 或 FlatBuffers)相比,功能较为简单,不支持版本控制、数据描述等高级特性。