书接上回,上回完成了数据的二进制转换,在这回需要对结构体的数据成员实现 反射 (reflection )
何为反射? 在其他大多数语言中有反射的机制,这种机制能在运行态时能够获取到一个类中的所有属性和方法的集合,比如 JS 就能通过Object.getOwnPropertyNames()
方法返回其自身属性集合:
var arr = ["a" , "b" , "c" ];var list = Object .getOwnPropertyNames (arr)console .log (list)
输出:
['0' , '1' , '2' , 'length' ]
于是可以使用得到的这个数组进行遍历操作:
list.forEach ((item )=> { console .log (`arr::${item} ()` , "-->" , arr1[item]) })
输出:
arr ::0 () --> aarr ::1 () --> barr ::2 () --> carr ::length () --> 3
这样也不难看出如果反射机制运用在数据绑定和转换中确实会带来许多的便利
如何实现? 反射机制在C++中有两种实现方式:
静态反射:采用模板的方式在编译期间决定
动态反射:类似于 Json 库实现的方式
这里的使用场景只是用于数据的转换,因此采用静态的方法进行实现 在静态反射需要使用的一个很重要的工具类: tuple
(元组) 。元组需要在 C++ 中明确元素的类型,这样才能在编译期决定代码的排列,即:
std::tuple<int , char , std::string> _tuple { 1 , 'F' , "ABCD" }; auto i = std::get <0 >(_tuple);auto c = std::get <1 >(_tuple);auto str = std::get <2 >(_tuple);
对 tuple
的简单分析 由于元组需要在编译器期间决定数据类型,因此需要在在编写期间进行赋值由编译器进行推导或者明确类型交予后续的赋值处理。那么元组的实际组成是什么样的呢?以及它如何进行构成的?
这里的代码分析基于 MSVC 14.40.33807
查看&精简源代码可知:
template <class _Ty >struct _Tuple_val { constexpr _Tuple_val() : _Val() {} template <class _Other > constexpr _Tuple_val(_Other&& _Arg) : _Val(_STD forward<_Other>(_Arg)) {} ... _Ty _Val; }; template <class _This , class ... _Rest>class tuple <_This, _Rest...> : private tuple<_Rest...> { public : using _This_type = _This; using _Mybase = tuple<_Rest...>; ... _Tuple_val<_This> _Myfirst; };
不难看出其实元组实际上是通过递归继承自我 的方式来实现的,而且最底层的构成并不是十分复杂,其他的基本是对接标准库接口或者自身的 API:
classDiagram
direction RL
class `_Tuple_val<_Ty>` {
+_Ty _Val;
+...()
}
class `tuple<_This, _Rest...>`{
+_Tuple_val<_This> _Myfirst;
+...()
}
`tuple<_This, _Rest...>` *-- `_Tuple_val<_Ty>`
`tuple<_This, _Rest...>` <|-- `tuple<_This, _Rest...>`
那 std::get
是如何拿到元素值的呢?查看对应的源码:
template <class _This , class ... _Rest>struct _MSVC_KNOWN_SEMANTICS tuple_element<0 , tuple<_This, _Rest...>> { using type = _This; using _Ttype = tuple<_This, _Rest...>; }; template <size_t _Index, class _This , class ... _Rest>struct _MSVC_KNOWN_SEMANTICS tuple_element<_Index, tuple<_This, _Rest...>> : tuple_element<_Index - 1 , tuple<_Rest...>> {}; _EXPORT_STD template <size_t _Index, class ... _Types> _NODISCARD constexpr tuple_element_t <_Index, tuple<_Types...>>& get (tuple<_Types...>& _Tuple) noexcept { using _Ttype = typename tuple_element<_Index, tuple<_Types...>>::_Ttype; return static_cast <_Ttype&>(_Tuple)._Myfirst._Val; }
可以看出对应的 get
在知晓传入元组的类型后调用了 tuple_element
结构体来对传入的下标进行递归减少,当 _Index
为 0 时取得对应的元素类型,然后对传入的元组进行转换拿到对应的元素值。同时也可以看出编译器会强制要求元组的顺序性,保证 get
返回正确数据
实现 对于需要进行数据绑定和转换的结构体来说,可以指定一个元组来保存所需要成员的相关参数,这里列举三个:
类型
名称
地址
因此需要定义个结构体来进行保存:
template <typename Class, typename T>struct PropertyMeta { constexpr PropertyMeta (T Class::*member, const char * name) : Name(name), Member(member) { } using Type = T; const char * Name; T Class::*Member; };
定义了之后就可以使用 tuple
来保存所需要处理的成员:
struct A { int num; char txt; static constexpr auto Tuple = std::make_tuple ( tools::PropertyMeta <A, int >(&A::num, "num" ), tools::PropertyMeta <A, char >(&A::txt, "txt" )); }; int main () { A a { .num = 1 , .txt = 'F' }; std::cout << "(" << std::get <0 >(A::Tuple).Name << ", " << a.*(std::get <0 >(A::Tuple).Member) << ")" << std::endl; std::cout << "(" << std::get <1 >(A::Tuple).Name << ", " << a.*(std::get <1 >(A::Tuple).Member) << ")" << std::endl; }
同时为了便于使用,还是需要定义合适的函数模板以及宏,因此这一块的最终实现如下:
namespace tools {#define VAR_PROPERTY_SCOPE(CLASS, ...) \ using __Var = CLASS; \ static constexpr auto __Property = std::make_tuple(__VA_ARGS__); #define VAR_PROPERTY(MEMBER) \ tools::PropertyMeta(&Elem::MEMBER, #MEMBER) #define VAR_PROPERTY_SCHEME(MEMBER, MEMBER_NAME) \ tools::PropertyMeta(&Elem::MEMBER, MEMBER_NAME) namespace _impl {template <typename Class, typename T>struct PropertyMeta { constexpr PropertyMeta (T Class::*member, const char * name) : Name(name), Member(member) { } using Type = T; const char * Name; T Class::*Member; }; } template <typename Class, typename T>constexpr auto PropertyMeta (T Class::*member, const char * name) { return _impl::PropertyMeta <Class, T>(member, name); } }
因此在结构体中调用就可以变成
struct A { int num; char txt; static constexpr auto Tuple = std::make_tuple ( tools::PropertyMeta <A, int >(&A::num, "num" ), tools::PropertyMeta <A, char >(&A::txt, "txt" )); };
➡
struct A { int num; char txt; VAR_PROPERTY_SCOPE (A, VAR_PROPERTY (num), VAR_PROPERTY (txt)) };
那么现在有个问题,如何对元组进行遍历呢?毕竟无法从元组中获取对应的个数信息,然后通过 get
来进行依次获取。对于这个问题在不同的版本中有各自对应的解决方案,这里使用 C++17 中的 std::apply
来解决:
#if _HAS_CXX17 template <typename ... Args, typename Handler>void PropertyForeach (const std::tuple<Args...>& tuple, Handler&& handler) { std::apply ([&](const Args&... args) { (handler (args), ...); }, tuple); } #endif
有了上面这些处理后,对于正反序列化来说就可以更进一步的完善:
template <typename T>void Serialized (_impl::buffer::Type &buffer, const T &value) { PropertyForeach (T::__Property, [&](auto elem) { _impl::ToBuffer <typename decltype (elem)::Type>()(buffer, value.*(elem.Member)); }); } template <typename T>bool Deserialized (T &value, const _impl::buffer::Type &buffer) { int offset = 0 ; try { PropertyForeach (T::__Property, [&](auto elem) { auto len = _impl::FromBuffer <typename decltype (elem)::Type>()(value.*(elem.Member), buffer, offset); if (len < 0 ) throw std::logic_error ("tools::cvt => bad parsed" ); offset += len; }); } catch (...) { return false ; } return true ; }
这样就实现了对结构体的成员进行数据绑定和转换
测试 同上节,也是进行来回转换进行比对:
struct Complex { int8_t int_8; int16_t int_16; int32_t int_32; int64_t int_64; float f_4; double f_8; std::string str; std::vector<int > int_arr; std::vector<std::string> str_arr; VAR_PROPERTY_SCOPE (Complex, VAR_PROPERTY (int_8), VAR_PROPERTY (int_16), VAR_PROPERTY (int_32), VAR_PROPERTY (int_64), VAR_PROPERTY (f_4), VAR_PROPERTY (f_8), VAR_PROPERTY (str), VAR_PROPERTY (int_arr), VAR_PROPERTY (str_arr) ) }; template <typename T>bool CvtComplexTest (const T& val, T& result) { std::vector<uint8_t > buffer; if constexpr (!std::is_class_v<T>) { return false ; } tools::cvt::Bianry::Serialized (buffer, val); return tools::cvt::Bianry::Deserialized (result, buffer); }