xpack原理分析

经常有网友问xpack(原来的x2struct)的实现原理,这里用一个文章大概讲解一下。以下分析均基于Ubuntu 16.04环境进行。写的比较简略,有疑惑的可以在评论区留言,我慢慢补全。

xpack主要包含两部分:

  1. 通过XPACK宏给结构体添加encode/decode函数
  2. 实现相应的encoder/decoder类

XPACK宏展开

展开结果

先说结论。

可以写一段简单的代码辅助分析(保存为test.cpp):

#include "xpack/xpack.h"
  
struct TestStruct {
    int uid;
    std::string name;
    std::string email;
    XPACK(A(uid, "id"), O(name, email));
};

然后执行命令(需要安装astyle):

g++ -E test.cpp | astyle | grep -A 100 TestStruct

可以看到展开的代码如下(经过一些调整)

struct TestStruct {
    int uid;
    std::string name;
    std::string email;
public:
    // 用于表征这个结构体添加了XPACK宏
    static bool const __x_pack_value = true;

    // 生成decode函数
    // __X_PACK_DOC 是decoder,命名不是很好
    // __X_PACK_ME 是当前这个结构体,不用this的原因是为了XPACK_OUT
    template<class __X_PACK_DOC, class __X_PACK_ME>
    void __x_pack_decode(__X_PACK_DOC& __x_pack_obj, __X_PACK_ME &__x_pack_self, const xpack::Extend *__x_pack_extp) {
        (void)__x_pack_extp;
        // 每个字母包含会生成一个代码块。核心就是调用__x_pack_obj.decode函数
        // 传入的是变量的名字(别名特殊处理),变量本身,和extend控制参数

        // 这里对应的是A(uid, "id")
        {
            int __x_pack_flag = 0 | 0 ;
            {
                static xpack::Alias __x_pack_alias("uid", "id");
                xpack::Extend __x_pack_ext(__x_pack_flag, &__x_pack_alias);
                const char *__new_name = __x_pack_alias.Name(__x_pack_obj.Type());
                __x_pack_obj.decode(__new_name, __x_pack_self.uid, &__x_pack_ext);
            }
        }
        // 这里对应的是O(name, email)
        {
            int __x_pack_flag = 0 | 0 ;
            xpack::Extend __x_pack_ext(__x_pack_flag, __null);
            __x_pack_obj.decode("name", __x_pack_self.name, &__x_pack_ext);
            __x_pack_obj.decode("email", __x_pack_self.email, &__x_pack_ext);
        }
    }

    // 生成encode函数
    template <class __X_PACK_DOC, class __X_PACK_ME>
    void __x_pack_encode(__X_PACK_DOC& __x_pack_obj, const __X_PACK_ME &__x_pack_self, const xpack::Extend *__x_pack_extp) const {
        (void)__x_pack_extp;
        {
            int __x_pack_flag = 0 | 0 ;
            {
                static xpack::Alias __x_pack_alias("uid", "id");
                xpack::Extend __x_pack_ext(__x_pack_flag, &__x_pack_alias);
                const char *__new_name = __x_pack_alias.Name(__x_pack_obj.Type());
                __x_pack_obj.encode(__new_name, __x_pack_self.uid, &__x_pack_ext);
            }
        }
        {
            int __x_pack_flag = 0 | 0 ;
            xpack::Extend __x_pack_ext(__x_pack_flag, __null);
            __x_pack_obj.encode("name", __x_pack_self.name, &__x_pack_ext);
            __x_pack_obj.encode("email", __x_pack_self.email, &__x_pack_ext);
        }
    };
};

从代码可以看出,XPACK核心就是给结构体添加了两个模板函数:

  • __x_pack_decode。通过模板传入decoder,对每个包含的变量调用decode函数
  • __x_pack_encode。通过模板传入encoder,对每个包含的变量调用encode函数

展开过程

大概的展开过程可以看xpack.h的注释。

这里面主要用到的是获取宏参数个数的一个小技巧,核心的宏是这里。我们可以把这个宏简化一下:

#define X_PACK_COUNT(LEVEL, ACTION, _2,_1,N,...) LEVEL##N

可以理解为X_PACK_COUNT这个宏的展开结果就是第一个参数(LEVEL)和第五个参数(N)的拼接。可以看看调用这个宏的地方

#define X_PACK_N(LEVEL, ACTION, ...) X_PACK_COUNT(LEVEL, ACTION, __VA_ARGS__, _2, _1)

也就是把X_PACK_N后面的不定参数在占位符之前展开了。如果X_PACK_N带一个参数a,那么展开的效果就是X_PACK_COUNT(LEVEL, ACTION, a, _2, _1)那么X_PACK_COUNT宏的第五个参数就是_1,那么LEVEL##N就是LEVEL_1。如果带两个参数,那么就是X_PACK_COUNT(LEVEL, ACTION, a, b, _2, _1),那么结果就是LEVEL_2,从而实现了根据宏参数个数展开成不同的宏的效果。

XPACK宏并不是直接包含变量,而是需要通过一个字母来包含这些变量。原因有:

  • 增强表达能力。比如可以支持别名、可选、必选等。
  • 支持更多的变量。通过上面的分析可以知道,X_PACK_COUNT能加入的变量数等于占位符的数量,目前是_99,也就是最多只能包含99个变量。这里通过两层,则把变量数扩展到了99*99(比如用99个O,每个O里面99个变量)

这个带来的问题就是展开的过程变得复杂了,需要展开两次。然后因为宏展开的过程是禁止“递归”的,也就是展开形成的结果不允许是之前的宏。这也是为何定义了两份类似的宏(比如X_PACK_N和X_PACK_N2)的原因,如果中途复用X_PACK_N这个宏,预处理器会停止展开,因为它认为循环了。

 

encoder和decoder

通过XPACK宏,给结构体添加了encode和decode函数,函数里面会遍历所有的变量,每个变量都会调用模板传递的obj的encoe/decode函数。这个时候就可以通过编写不同的encoder/decoder来实现不同的功能了。下面以decoder为例说明。

decoder要实现一个名为decode的模板函数,核心问题是如何针对各种不同的类型编写不同的decode函数,这里需要大量用到C++的SFINAE特性。xpack目前支持json/xml/bson三种编码,这里通过xdecoder.h把一些公共类型包装了进去,各个类型的decoder只需要写基础类型和一些特定接口即可。但是xdecoder.h这种并不一定适合自定义decoder,需要根据自己的需求来。

 

自定义encoder/decoder

XPACK宏给结构体/类添加了__x_pack_decode和__x_pack_decode两个函数,两个函数都是遍历了结构体的变量,可以自己写encoder或者decoder(选哪个看需求)来实现一些功能。比如之前有网友提过一个需求:根据变量名给目的结构体的变量赋值。比如SetFields(a, b, "uid,name")就是b.uid = a.uid; b.name = a.name;这里用decoder更合适一些,因为要改变目标结构体的值。代码如下:

#include <iostream>
#include <string>
#include "xpack/json.h"

using namespace std;


// assign value by name
class Assign {
public:
    // 构造函数,把源、目的结构体地址,需要设置的变量名存进来
    Assign(const std::vector<std::string>&vs, const unsigned char *src, unsigned char *dst) {
        for (size_t i=0; i<vs.size(); ++i) {
            fields.insert(vs[i]);
        }
        this->src = src;
        this->dst = dst;
    }
public:
    // 实现一个decode,通过地址偏移获得变量指针,然后赋值
    template<class T>
    bool decode(const char *key, T&val, const xpack::Extend *ext) {
        // 如果变量在变量名列表里面
        if (fields.end() != fields.find(key)) {
            // 通过val获取偏移,通过偏移获得源的指针
            unsigned int offset = (unsigned int)((unsigned char*)&val - dst);
            val = *((T*)(src+offset));
        }
        return true;
    }
private:
    std::set<std::string> fields;
    const unsigned char *src;
    unsigned char *dst;
};

template <class T>
void SetFields(const T&src, T&dst, const std::string&fields) {
    std::vector<std::string> vs;
    xpack::Util::split(vs, fields, ',');
    if (vs.size() == 0) {
        return;
    }

    Assign obj(vs, (unsigned char*)(&src), (unsigned char*)(&dst));
    dst.__x_pack_decode(obj, dst, NULL);
}


// 以下是测试代码
struct Data {
    int a;
    int b;
    std::string c;
    XPACK(O(a,b,c));
};


int main(int argc, char *argv[]) {
    (void)argc;
    (void)argv;

    Data d1;
    Data d2;
    d1.a = 5;
    d1.b = 10;
    d1.c = "hello";
    d2.b = 9;
    SetFields<Data>(d1, d2, "a,c");
    cout<<d2.a<<','<<d2.c<<endl;
    return 0;
}

 

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值