C++序列化

主体类实现序列化和反序列化功能,用的模版元编程。通过重载<<达到流式编码效果。

代码里没有用using namespace,不习惯这种用法,习惯了显式namespace。

编码结果参考YaZi, 将变量编码为: 1字节(类型) + n字节(数据)。

对容器类编码为: 1字节(类型) + 5字节(长度,其中1字节代表类型,4字节代表长度) + n字节(数据)。

类型

类型通过枚举类表示

enum my_enum
    {
        UNKNOW = -1,
        INT = 0,
        INT64,
        BOOL,
        CHAR,
        FLOAT,
        DOUBLE,
        CHARPTR,
        STRING,
        VECTOR,
        DEQUE,
        SET,
        MULTISET,
        MAP,
        MULTIMAP,
        LIST,
        CUSTOM
    };

 序列化实现

template<class T>
Serialize& operator << (T data)
{
    this->write(data);
    this->obj_num++;
    return *this;
}

可以看到调用的是write方法,在实现中对write方法做了一些重载,主要处理

1. 基本类型,int32,bool之类的,非指针类型。

// 基本数据类型
template<class T, class  = std::enable_if_t<std::is_pod_v<std::decay_t<T>>, void>>
void write(T data)
{
    this->writeType(data);
    this->write((char*)&data, sizeof(data));
}

对基本类型处理直接转为char*指针,本质是将数据转为字节流传输,调用memcpy_s方法写入字节流。

void Serialize::write(const char* data, int len)
{
    this->reserve(len);
    int size = this->m_buf.size();
    this->m_buf.resize(size + len);
    memcpy_s(&this->m_buf[size], size+len, data, len);
}

其中std::enable_if_t<std::is_pod_v<std::decay_t<T>>, void>匹配基本数据类型,个人感觉std::enable_if_t主要在模版中提供一种筛选功能,具体可以看官网介绍。std::decay_t用来进行类型擦除,主要擦除const以及引用这些。

2. stl标准容器类型

下边是write的另外两个重载,用来处理stl标准容器类型

// 类list类型
template<class T>
std::enable_if_t<is_array_v<T>, void> write(const T& data)
{
    this->writeType(data);
    int len = data.size();
    this->write(len);
    for(auto iter = data.begin(); iter != data.end(); iter++)
    {
        this->write(*iter);
    }
}
// k,v类型
template<class T>
std::enable_if_t<is_KV_v<T>, void> write(const T& data)
{
    this->writeType(data);
    int len = data.size();
    this->write(len);
    for(auto iter = data.begin(); iter != data.end(); iter++)
    {
        this->write(iter->first);
        this->write(iter->second);
    }
}

3.string类型

对string类型做单独处理,因为需要调用c_str将string转为char*

// std::string类型
template<class T>
std::enable_if_t<std::is_same_v<std::decay_t<T>, std::string>, void> write(T data)
{
    this->writeType(data);
    this->write(data.c_str());
}

4. char*类型

没有考虑对其他类型数组的处理,主要原因是除了char类型数组,其他数组没有明显的结束符,需要额外传递长度参数,建议直接使用stl标准库的list/vector。而char*类型一般表示字符串,有明显的'\0'结束符,因此直接对char*类型做了偏特化。这样所有类型只需要传递数据本身,在调用时没有差别。

void Serialize::write(const char* data)
{
    int len = strlen(data) + 1;
    this->write(len);
    this->write(data, len);
}

5. 自定义类型处理 

此处借鉴了YaZi大佬的思路,通过宏获取自定义类中需要序列化的属性,使用变长模版参数进行处理。唯一感觉难受的就是需要将该方法做成public,有点影响封装。

关于自定义类型,需要继承SerializeAble类并且调用宏SERIALIZEABLE将自定义类中需要序列化的字段写入。

void Serialize::write(SerializeAble& serialize_able)
{
    serialize_able.serialize(*this);
}

#define SERIALIZEABLE(...)                                  \
    void serialize(zdsj::Serialize& stream) const override  \
    {                                                       \
        auto type_name = zdsj::CUSTOM;                      \
        stream.write_args(type_name, __VA_ARGS__);          \
    }                                                       \
    bool unSerialize(zdsj::Serialize& stream) override      \
    {                                                       \
        return stream.read_args(__VA_ARGS__);               \
    }                                                       \

template<class T, class ...Args>
void write_args(T& data, Args& ...args)
{
    this->write(data);
    this->write_args(args...);
}

void write_args()
{
    
}

class A : public zdsj::SerializeAble
{
public:
    A()
    {
        
    }

    void show()
    {
        std::cout << "a=" << a << " b=" <<  b << std::endl;
    }

    SERIALIZEABLE(a, b)
private:
    int a = 2;
    float b = 23.0;
};

序列化中对stl容器类型判断方法

这里借鉴了其他大佬的思路,通过模版匹配的方式判断

template<typename T,typename... Types>
struct IsContainerType
{
    static const bool value = false;
    static const my_enum type = zdsj::CUSTOM;
};

// Vector类型为true
template<typename T,typename... Types>
struct IsContainerType<std::vector<T, Types...>>
{
    static const bool value = true;
    static const my_enum type = zdsj::VECTOR;
};

// deque类型
template<typename T,typename... Types>
struct IsContainerType<std::deque<T, Types...>>
{
    static const bool value = true;
    static const my_enum type = zdsj::DEQUE;
};

// set类型
template<typename T,typename... Types>
struct IsContainerType<std::set<T, Types...>>
{
    static const bool value = true;
    static const my_enum type = zdsj::SET;
};

// multiset类型
template<typename T,typename... Types>
struct IsContainerType<std::multiset<T, Types...>>
{
    static const bool value = true;
    static const my_enum type = zdsj::MULTISET;
};

// map类型
template<typename K,typename V,typename... Types>
struct IsContainerType<std::map<K, V, Types...>>
{
    static const bool value = true;
    static const my_enum type = zdsj::MAP;
};

// multimap类型
template<typename K,typename V,typename... Types>
struct IsContainerType<std::multimap<K, V, Types...>>
{
    static const bool value = true;
    static const my_enum type = zdsj::MULTIMAP;
};

// list类型
template<typename T,typename... Types>
struct IsContainerType<std::list<T, Types...>>
{
    static const bool value = true;
    static const my_enum type = zdsj::LIST;
};

// 定义获取容器类型的模板
template<typename T,typename... Types>
constexpr bool is_container_v = IsContainerType<T, Types...>::value;

// 定义获取容器类型的模板
template<typename T,typename... Types>
constexpr my_enum is_container_t = IsContainerType<T, Types...>::type;

// 定义获取容器类型的模板
template<typename T,typename... Types>
constexpr bool is_array_v = IsContainerType<T, Types...>::type == zdsj::VECTOR ||
    IsContainerType<T, Types...>::type == zdsj::SET ||
    IsContainerType<T, Types...>::type == zdsj::MULTISET ||
        IsContainerType<T, Types...>::type == zdsj::DEQUE ||
            IsContainerType<T, Types...>::type == zdsj::LIST;

template<typename T,typename... Types>
constexpr bool is_KV_v = IsContainerType<T, Types...>::type == zdsj::MAP ||
    IsContainerType<T, Types...>::type == zdsj::MULTIMAP;

反序列化

反序列化与序列化操作相反,代码已上传gitee

找到数据起始地址所在索引,将其转换为指针后直接解引用,提前加了判断,判断类型是否匹配,防止解引用时数据出错,其他数据格式同理:

// 基本数据类型
template<class T, class = std::enable_if_t<std::is_pod_v<std::decay_t<T>>, void>>
bool read(T& data)
{
    auto read_type = my_enum(this->m_buf[this->m_pos]);
    this->m_pos++;
    if(this->checkType(data, read_type))
    {
        data = *(T*)(&this->m_buf[this->m_pos]);
        this->m_pos += sizeof(T);
        return true;
    }else
    {
        return false;
    }
}

由于反序列化实现的方式,在使用时需要反序列化顺序与序列化相同,否则容易出现类型不匹配导致反序列化失败的情况。

其实本质上不需要对类型进行编码,只要保证反序列化时与序列化顺序相同即可,但是那样因为缺少类型判断,如果顺序不同,容易出现数据读取错误的问题。

代码地址:Serialize: c++序列化

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++ 序列化是将对象转换为字节流的过程,这样可以将对象存储到磁盘或网络中,并在需要时重新读取。以下是一个简单的示例: ```c++ #include <iostream> #include <fstream> #include <string> class Person { public: Person() = default; Person(std::string name, int age) : m_name(name), m_age(age) {} std::string getName() const { return m_name; } int getAge() const { return m_age; } friend std::ostream& operator<<(std::ostream& os, const Person& p) { os << "Name: " << p.m_name << ", Age: " << p.m_age << std::endl; return os; } friend std::istream& operator>>(std::istream& is, Person& p) { is >> p.m_name >> p.m_age; return is; } private: std::string m_name; int m_age; }; int main() { // 创建 Person 对象 Person p1("Tom", 30); // 将对象序列化写入文件 std::ofstream ofs("person.dat", std::ios::binary); ofs.write(reinterpret_cast<const char*>(&p1), sizeof(p1)); ofs.close(); // 从文件中读取序列化的对象 std::ifstream ifs("person.dat", std::ios::binary); Person p2; ifs.read(reinterpret_cast<char*>(&p2), sizeof(p2)); ifs.close(); std::cout << p2; return 0; } ``` 在这个示例中,我们定义了一个名为 `Person` 的类,它有两个成员变量:姓名和年龄。我们还重载了输入输出运算符,以便我们可以将对象输出到控制台或从控制台读取对象。 在 `main` 函数中,我们首先创建一个 `Person` 对象 `p1`,然后将其序列化写入名为 `person.dat` 的二进制文件中。接下来,我们从文件中读取序列化的对象并将其存储在 `p2` 中。最后,我们将 `p2` 输出到控制台。 请注意,在将对象写入文件时,我们使用了 `reinterpret_cast` 将指向 `Person` 对象的指针转换为 `char` 指针。同样,在从文件读取对象时,我们也使用了 `reinterpret_cast` 将 `char` 指针转换为 `Person` 对象指针。这是因为我们正在处理字节流,而不是对象本身。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值