C++序列化和反序列化工具
在开发时我们经常会遇到需要序列号和反序列化得操作。比如将程序配置导出下次打开再从配置中读取,通常你可以将信息保存成json、xml或ini格式,让后再解析出来。更常见的情形是我们通过网络发送和接收数据,也涉及到序列化和反序列化操作,当让你可以使用protobuf、msgpack等序列化库。但是如果你只是个简单程序,或者序列化和反序列化的业务不是很重,不想搞得那么复杂有什么简单的方法吗?
最直接的方法
直接按字节拷贝,见代码:
#include <string>
bool encode(const std::string& s, int32_t a, int32_t b,
int32_t c, std::string& buf) {
int32_t d = static_cast<int32_t>(s.length());
if (buf.size() < (sizeof(int32_t) * 4 + d))
return false;
std::size_t pos = 0;
memcpy(buf.data() + pos, &a, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(buf.data() + pos, &b, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(buf.data() + pos, &c, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(buf.data() + pos, &d, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(buf.data() + pos, s.data(), d);
return true;
}
bool decode(const std::string& buf, int32_t& a, int32_t& b,
int32_t& c, std::string& out) {
if (buf.size() < (sizeof(int32_t) * 4))
return false;
std::size_t pos = 0;
memcpy(&a, buf.data() + pos, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(&b, buf.data() + pos, sizeof(int32_t));
pos += sizeof(int32_t);
memcpy(&c, buf.data() + pos, sizeof(int32_t));
pos += sizeof(int32_t);
int32_t d = 0;
memcpy(&d, buf.data() + pos, sizeof(int32_t));
pos += sizeof(int32_t);
out = std::string(buf.data() + pos, d);
return true;
}
int main()
{
std::string buf;
buf.resize(1024);
encode("test", 1, 2, 3, buf);
std::string out;
int a = 0, b = 0, c =0;
decode(buf, a, b, c, out);
return 0;
}
或者简单封装下:
struct TestData {
int32_t a =0;
int32_t b = 0;
int32_t c = 0;
std::string s;
bool encode(std::string& buf) {
...
}
bool decode(const std::string& buf) {
...
}
};
TestData in{ 1, 2, 3, "test" };
std::string buf;
buf.resize(1024);
in.encode(buf);
TestData out;
out.decode(buf);
使用stringstream
可以看到上面的代码还是比较繁琐的,不仅需要手动计算字符索引位置,还要直接使用memcpy函数。对于这个问题c++的stringstream可以简化不少代码:
struct TestData {
int32_t a =0;
int32_t b = 0;
int32_t c = 0;
std::string s;
bool encode(std::string& buf) {
int32_t d = static_cast<int32_t>(s.length());
std::ostringstream ss;
ss.write((const char*)&a, sizeof(a));
ss.write((const char*)&b, sizeof(b));
ss.write((const char*)&c, sizeof(c));
ss.write((const char*)&d, sizeof(d));
ss << s;
buf = ss.str();
return ss.good();
}
bool decode(const std::string& buf) {
std::istringstream ss(buf);
int32_t d = 0;
ss.read((char*)&a, sizeof(a));
ss.read((char*)&b, sizeof(b));
ss.read((char*)&c, sizeof(c));
ss.read((char*)&d, sizeof(d));
ss >> s;
return ss.good();
}
};
可以明显的看出代码比前面的要简洁优雅多了,但是这里面却有个比较严重的问题,无论是encode还是decode都需要构建stringstream,而stringstream会在内部构建一个新的streambuf对象,无论是输入还是输出都需要一次拷贝操作。
我们还有strstream
struct TestData {
int32_t a =0;
int32_t b = 0;
int32_t c = 0;
std::string s;
bool encode(std::string& buf) {
int32_t d = static_cast<int32_t>(s.length());
std::ostrstream ss(buf.data(), buf.length());
ss.write((const char*)&a, sizeof(a));
ss.write((const char*)&b, sizeof(b));
ss.write((const char*)&c, sizeof(c));
ss.write((const char*)&d, sizeof(d));
ss << s;
return ss.good();
}
bool decode(const std::string& buf) {
std::istrstream ss((char*)buf.data(), buf.length());
ss.seekp(buf.length()); // 设置写入位置(写入位置>读取位置才代表有数据可读)
int32_t d = 0;
ss.read((char*)&a, sizeof(a));
ss.read((char*)&b, sizeof(b));
ss.read((char*)&c, sizeof(c));
ss.read((char*)&d, sizeof(d));
ss >> s;
return ss.good();
}
};
stringstream使用的人比较多,但是知道strstream的就少的多了。strstream可以使用已有的内存做rdbuf, 但是很不幸的是这么好用的工具竟然被c++标准给废弃了[1][2]。
最后大招,自己造轮子
struct serialization_streambuf : public std::streambuf
{
serialization_streambuf(char* s, std::size_t n)
{
setp(s, s, s + n); // set write buffer pointer
setg(s, s, s + n); // set read buffer pointer
}
};
struct TestData {
int32_t a =0;
int32_t b = 0;
int32_t c = 0;
std::string s;
bool encode(std::string& buf) {
int32_t d = static_cast<int32_t>(s.length());
serialization_streambuf osrb(buf.data(), buf.length());
std::ostream ss(&osrb);
ss.write((const char*)&a, sizeof(a));
ss.write((const char*)&b, sizeof(b));
ss.write((const char*)&c, sizeof(c));
ss.write((const char*)&d, sizeof(d));
ss << s;
return ss.good();
}
bool decode(const std::string& buf) {
serialization_streambuf osrb((char*)buf.data(), buf.length());
std::istream ss(&osrb);
int32_t d = 0;
ss.read((char*)&a, sizeof(a));
ss.read((char*)&b, sizeof(b));
ss.read((char*)&c, sizeof(c));
ss.read((char*)&d, sizeof(d));
ss >> s;
return ss.good();
}
};
可以看到,其实也很简单。就是模仿strstream为iostream构建streambuf就行了。这样不仅享受到了iostream类给我们提供的便利,也克服了stringstream需要内存拷贝的缺陷。
参考引用
[1] std::strstream[EB/OL].
https://en.cppreference.com/w/cpp/io/strstream
[2] Why was std::strstream deprecated?[EB/OL].
https://stackoverflow.com/questions/2820221/why-was-stdstrstream-deprecated