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++序列化

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值