亳州是网站建设,搜外友链平台,广告推广方案,百度推广优化是什么意思序
本文我们看下用宏来实现反射#xff0c;在一些伙伴使用c版本还不是那么高的情况下但又需要反射的一些技巧#xff0c;这里使用的代码是iguana里的实现#xff0c;我对它关于反射的地方提炼一下#xff0c;稍微改动了下。iguana是比较优秀的序列化库#xff0c;其中使用…序
本文我们看下用宏来实现反射在一些伙伴使用c版本还不是那么高的情况下但又需要反射的一些技巧这里使用的代码是iguana里的实现我对它关于反射的地方提炼一下稍微改动了下。iguana是比较优秀的序列化库其中使用反射为基础性能很好。现在在yalantinglibs中也可以找到。 当然使用的时候可以直接使用iguana我这里解释下其中的相关原理。
如何使用
以如下Person这个结构体为例
struct Person{int a;float b;
};
REFLECTION(Person, a, b)这里结构体就是普通的结构体不过需要用户做的是需要定义REFLECTION宏其中第一个参数是类结构体名然后是各个成员名。 然后其实就可以使用反射了:
using Members decltype(iguana_reflect_members(std::declvalPerson()));
std::cout Members::value() std::endl; // countauto membersPtr Members::apply_impl(); // ptrtuple
Person p{};
p.*std::get0(membersPtr) 34;
p.*std::get1(membersPtr) 4.1f;std::cout p.a std::endl; // 34
std::cout p.b std::endl; // 4.1REFLECTION中会生成一个iguana_reflect_members函数该函数简单展示下
auto iguana_reflect_members(STRUCT_NAME const ) { struct reflect_members { // ...(略)}; return reflect_members{};
}可以看到iguana_reflect_members函数内部定义了一个用来提供成员反射信息的b结构体然后构造并返回。 继续返回到使用示例那里第一句通过decltype和declval搭档拿到了iguana_reflect_members返回值类型。第二句我们先打印出来Person这个结构体的成员个数。然后再使用Members::apply_impl函数获取到Person的成员指针。这里使用成员指针就可以对其成员进行访问了。返回值的类型是tuple我们使用std::get来对tuple进行遍历访问。
如何实现
那么如何实现获取到成员的个数及存储成员指针这些呢我们去揭开REFLECTION的真面目
#define REFLECTION(STRUCT_NAME, ...) \MAKE_META_DATA_IMPL(STRUCT_NAME, GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)看上去很简单调用MAKE_META_DATA_IMPL的宏MAKE_META_DATA_IMPL需要STRUCT_NAMEcount以及所有的剩余参数也就是类的各个成员。可以看到GET_ARG_COUNT可以获取到成员的个数。
成员个数
那我们先去看下GET_ARG_COUNT的实现
#define MARCO_EXPAND(...) __VA_ARGS__
#define GET_ARG_COUNT_INNER(...) MARCO_EXPAND(ARG_N(__VA_ARGS__))#define GET_ARG_COUNT(...) GET_ARG_COUNT_INNER(__VA_ARGS__, RSEQ_N())GET_ARG_COUNT调用GET_ARG_COUNT_INNER将成员和RSEQ_N拼接起来传递给ARG_N这个宏作为参数调用那就要看下ARG_N和RSEQ_N的声明
#define ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, \_9, _10, _11, _12, _13, _14, _15, \_16, _17, _18, _19, _20, _21, _22, \_23, _24, _25, _26, _27, _28, _29, \_30, _31, _32, N, ...) \N#define RSEQ_N() \32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, \5, 4, 3, 2, 1, 0ARG_N可以看到接收32个参数及N然后就能表示N。RSEQ_N()仅仅就是320的数字序列那么将成员(假设3个成员)和RSEQ_N()组合起来就是类似这样
member1, member2, member3, 32, 31, 30, ... 0然后传递给ARG_N时member1对应_1member2对应_2member3对应_332对应_4… 这样计算参数个数就是33个成员 33320那么进一步这里N就是第33个元素再进一步可以这样理解如果没有成员仅仅RSEQ_N()传递进来时一一匹配到0正好对应N那么当前边加了3个成员那么就是将RSEQ_N()后移3个元素那就是N正好对应3也就是成员的个数。
成员指针
再次回到我们实现的最开始部分
#define REFLECTION(STRUCT_NAME, ...) \MAKE_META_DATA_IMPL(STRUCT_NAME, GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)GET_ARG_COUNT这个我们已经明白了那么我们继续去看下MAKE_META_DATA_IMPL宏做了什么
#define MAKE_META_DATA_IMPL(STRUCT_NAME, N, ...) \[[maybe_unused]] inline static auto \iguana_reflect_members(STRUCT_NAME const ) { \struct reflect_members { \constexpr decltype(auto) static apply_impl(){ \return std::make_tuple( \MAKE_ARG_LIST(N, STRUCT_NAME::FIELD, \__VA_ARGS__)); \} \using size_type \std::integral_constantsize_t, N; \constexpr static size_t value() { \ return size_type::value; \} \}; \return reflect_members{}; \}MAKE_META_DATA_IMPL这个宏就是定义了iguana_reflect_members(STRUCT_NAME const )这样一个函数大致的结构我们前边也说过值得注意的有两个点
因为参数会根据传入的类名各不一样所以不用担心函数签名重复的问题定义了函数内部结构体返回了内部结构体对象但是如我们最开始使用那样仅仅通过decval拿到这个内部结构体的类型而不会真正调用iguana_reflect_members函数。
继续看reflect_members结构体从简单的看起value函数就是返回刚刚传进来的N这里size_type就是一个值为N的结构体正好也是返回size_type::value, 所以就是N。 apply_impl函数里边稍微有点复杂因为成员的指针类型各不一样所以使用tuple来存放内部就是对MAKE_ARG_LIST的调用那我们也再跳转到实现瞧瞧
#define MACRO_CONCAT(A, B) MACRO_CONCAT1(A, B)
#define MACRO_CONCAT1(A, B) A##_##B#define MAKE_ARG_LIST(N, op, arg, ...) \MACRO_CONCAT(MAKE_ARG_LIST, N)(op, arg, __VA_ARGS__)由于宏的一些特性我们不得不使用MACRO_CONCAT1及MACRO_CONCAT对宏与一些字符进行拼接。这里是把MAKE_ARG_LIST和下划线以及N进行拼接那么MAKE_ARG_LIST实际上调用的是MAKE_ARG_LIST_N但是这里的N是实际的成员个数还是假定是3个成员那么调用就是这样的MAKE_ARG_LIST_3(op, arg, __VA_ARGS__)同时这里还用arg来拆出来第一个元素类似于我们解参数包方式。 那么我们还需要再次去看MAKE_ARG_LIST_3的实现
#define MAKE_ARG_LIST_1(op, arg, ...) op(arg)
#define MAKE_ARG_LIST_2(op, arg, ...) \op(arg), MARCO_EXPAND(MAKE_ARG_LIST_1(op, __VA_ARGS__))
#define MAKE_ARG_LIST_3(op, arg, ...) \op(arg), MARCO_EXPAND(MAKE_ARG_LIST_2(op, __VA_ARGS__))
#define MAKE_ARG_LIST_4(op, arg, ...) \op(arg), MARCO_EXPAND(MAKE_ARG_LIST_3(op, __VA_ARGS__))//...(略)#define MAKE_ARG_LIST_32(op, arg, ...) \op(arg), MARCO_EXPAND(MAKE_ARG_LIST_31(op, __VA_ARGS__))因为MAKE_ARG_LIST_N和成员个数有关这里也还是定义了32个宏中间我们省略了很多实现很简单先看MAKE_ARG_LIST_1,就是对op的调用再看MAKE_ARG_LIST_2首先对第一个参数进行op调用剩下的参数去调用MAKE_ARG_LIST_1然后使用逗号拼接。以此类推如果是32的话就是对32个参数分别op调用。 我们继续跳回到成员指针获取的那里
#define FIELD(t) tstruct reflect_members { constexpr decltype(auto) static apply_impl() {return std::make_tuple( MAKE_ARG_LIST(N, STRUCT_NAME::FIELD,__VA_ARGS__));}
}; 我们明白了MAKE_ARG_LIST的含义就是分别对各个参数进行op操作这里op正好对应于STRUCT_NAME::FIELD, FIELD就是一个封装了一个括号方便调用那也就是STRUCT_NAME::各个成员这也就是成员的指针。
最终推导展示
有了上边的讲解我们使用clion可以看到最开始Person的REFLECTION(Person, a, b)表示的是啥 [图]
增加成员类型
尽管可以通过指针获取到各个成员的类型但是为了使用方便我们在reflect_members中增加一个各个成员的类型我们使用类型列表来存放
templatetypename... Types
struct TypeList {};这样reflect_members中成员类型列表可能实现就是这样
struct reflect_members {using member_types TypeListMAKE_ARG_LIST(N, decltype, MAKE_ARG_LIST(N,STRUCT_NAME::FIELD, __VA_ARGS__));
};可以看到TypeList中使用两个MAKE_ARG_LIST嵌套实现首先对每个成员参数STRUCT_NAME::FIELD操作然后在对操作后的成员参数进行decltype操作以上面Person为例可以看到最终member_types是这样的
using member_types TypeListdecltype(Person::a), decltype(Person::b);然后我们如何使用TypeList需要配套一些操作方法我这里目前只实现了根据顺序来获取成员的类型类似这样
using MemberTypes Members::member_types;TypeByIndex0, MemberTypes::type a1 12; // int
TypeByIndex1, MemberTypes::type b1 12.87; // float我们也简单看下TypeByIndex如何实现
templateint Index, typename TL
struct TypeByIndex {using type typename TypeByIndexIndex - 1, typename ListPopTL::type::type;
};templatetypename TypeList
struct TypeByIndex0, TypeList {using type typename ListHeadTypeList::type;
};TypeByIndex模板元函数实现如上针对于Index为0进行特化那就是说只需要获取TypeList中第一个类型即可也就是这里的ListHead。否则就走主模板主模板是一个递归的将Index减1TypeList给pop出第一个元素也即ListPop操作继续调用TypeByIndex直到Index为0正好对应于相对应的类型。 我们也看下ListHead和ListPop的实现
templatetypename TL
struct ListHead;templatetypename Head, typename... Args
struct ListHeadTypeListHead, Args... {using type Head;
};templatetypename Tp
struct ListPop {using type TypeList;
};templatetypename Head, typename... Args
struct ListPopTypeListHead, Args... {using type TypeListArgs...;
};先看ListHead主模板仅仅是一个声明特化版本特化出来TypeListHead, Args...直接获取到Head。再来看ListPop主模板认为是一个空的TypeList特化模板则是特化出来TypeListHead, Args...形式那样正好把第一个元素和后边元素分开进一步拿到后边类型重新组装成新的TypeList。
总结
这里我们使用宏来实现了结构体或类成员的反射包括成员的个数成员的指针成员的类型。有了这些我们就可以做一些基本的操作了比如说一些序列化结构体等等。 同时我们还展示了TypeList及相关的简单操作。当然你如果需要的话也可以将TypeList操作丰富起来。
ref
https://github.com/qicosmos/iguana《C模板 第二版》