Graphene 源码阅读 ~ 数据库篇 ~ 对象反射

C++ 自身的 RTTI 提供了两个操作符: typeiddynamic_cast<>, 但这俩操作符的作用都仅限于运行时得到类型名而已, 很多时候尤其是对象序列化操作时, 光知道类型名是不够的, 我们还要知道类成员的类型, 成员名称等信息, 所以还是要靠各种编程库实现反射功能.

fc::reflector 就是石墨烯代码中反射实现库, 前一篇 对象序列化 里已经多次提到了, 可能严格来说, fc::reflector 实现的不是反射, 而是内省, 但这里就不过分解读定义了. 现在就让我们正式了解一下 fc::reflector 的实现.

对象反射

fc::reflector 采用了 c/c++ 经典又强大的宏来实现反射机制, 代码不多, 基本全都位于 <fc>/reflect/reflect.hpp 中. 首先定义了一个 reflector 模板类 (代码 4.1), 但这个模板类只是个向我们展示个架子, 实际上没有多少地方会真的去实例化这个模板类, 因为马上我们会看到 graphene 中所有的对象类型都是用 reflect.hpp 中定义的一系列宏生成了各自的 template<> struct reflector<> 特化类.

// 代码 4.1

 32 template<typename T>
 33 struct reflector{
 34     typedef T type;
 35     typedef fc::false_type is_defined;
 36     typedef fc::false_type is_enum;
 
 
 61     template<typename Visitor>
 62     static inline void visit( const Visitor& v );
    };

reflect.hpp 定义的宏里面, 最重要的当属 FC_REFLECT_DERIVED, 因为它是所有对象生成自己特化类时直接使用的宏, 我们以它为切入点分析一下.

// 代码 4.2

202 #define FC_REFLECT_DERIVED( TYPE, INHERITS, MEMBERS ) \
203 namespace fc {  \
204   template<> struct get_typename<TYPE>  { static const char* name()  { return BOOST_PP_STRINGIZE(TYPE);  } }; \
205 template<> struct reflector<TYPE> {\
206     typedef TYPE type; \
207     typedef fc::true_type  is_defined; \
208     typedef fc::false_type is_enum; \
209     enum  member_count_enum {  \
210       local_member_count = 0  BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_MEMBER_COUNT, +, MEMBERS ),\
211       total_member_count = local_member_count BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_BASE_MEMBER_COUNT, +, INHERITS )\
212     }; \
213     FC_REFLECT_DERIVED_IMPL_INLINE( TYPE, INHERITS, MEMBERS ) \
214 }; }

代码 4.2 贴出了此宏的完整定义, 三个参数 TYPE, INHERITS, MEMBERS 分别代表要反射对象的类型名, 父类们, 以及成员变量们, 其中 INHERITS, MEMBERS 这两个参数是 (a)(b)(c) 这种格式的序列, 下面会说到.

FC_REFLECT_DERIVED 首先为传入的 TYPE 类型定义了获取其名称的特化函数 template<> get_typename, get_typename 里用了一个 boost 库提供的工具宏 BOOST_PP_STRINGIZE, 它的原理其实就是 #define STRINIFY(a) #a, 这就实现了获取对象类型名的内省功能.

接下来的 template<> reflector 特化类中实现了更进一步的内省. 首先是成员个数, 定义了一个 member_count_enum 枚举, 枚举的两个成员 local_member_counttotal_member_count 分别代表此类型自己的成员数量以及包括所有父类成员加起来总共的成员数量.

local_member_count 这里又用了一个 boost 的宏 BOOST_PP_SEQ_FOR_EACH, BOOST_PP_SEQ_FOR_EACH 其实也很简单, 第一个参数是一个 "方法", 它的第三个参数就是上面说的 (a)(b)(c) 格式的序列, 这个宏的作用就是将 (a)(b)(c) 拆解成 a b c 然后分别和 + 一起传给第一个参数 FC_REFLECT_MEMBER_COUNT.

FC_REFLECT_MEMBER_COUNT 又是这样的定义的:

// 代码 4.3

 94 #define FC_REFLECT_MEMBER_COUNT( r, OP, elem ) \
 95   OP 1

可以看到它实际上只用到了第二个参数, 一例胜千言:

// 代码 4.4

BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_MEMBER_COUNT, +, (a)(b)(c) )

// 会展开成:

FC_REFLECT_MEMBER_COUNT(r, +, a) FC_REFLECT_MEMBER_COUNT(r, +, b) FC_REFLECT_MEMBER_COUNT(r, +, c)

// 进而展开成:

+ 1 + 1 + 1

// 于是 local_member_count 的值就变成了

local_member_count = 0  + 1 + 1 + 1

total_member_count 中也是类似, 感兴趣可以研究下, 要注意 total_member_count 里直接是遍历获取了所有父类的 total_member_count 成员.

接下来是最重要的 FC_REFLECT_DERIVED_IMPL_INLINE 宏, 这个宏定义了对象的 visit() 方法, 在上一章 对象序列化代码 3.5 里我们看到了每个对象在序列化时都会调用其 fc::reflector<T>::visit() 方法. 所以如果这里没有这个宏定义 visit(), 那对象的序列化也就废了. 这个宏的定义也很简单:

// 代码 4.5

 97 #define FC_REFLECT_DERIVED_IMPL_INLINE( TYPE, INHERITS, MEMBERS ) \
 98 template<typename Visitor>\
 99 static inline void visit( const Visitor& v ) { \
100     BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_BASE, v, INHERITS ) \
101     BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_MEMBER, v, MEMBERS ) \
102 }
// 代码 4.6

 73 #define FC_REFLECT_VISIT_BASE(r, visitor, base) \
 74   fc::reflector<base>::visit( visitor );


 85 #define FC_REFLECT_VISIT_MEMBER( r, visitor, elem ) \
 86 { typedef decltype(((type*)nullptr)->elem) member_type;  \
 87   visitor.TEMPLATE operator()<member_type,type,&type::elem>( BOOST_PP_STRINGIZE(elem) ); \
 88 }

这里面其实没有啥新内容了, 重点在FC_REFLECT_VISIT_MEMBER 定义, 这个定义其实也在 对象序列化 代码 3.7 中展示过了, 只不过那时展示的是只访问对象的一个成员的情况, visit() 方法最主要的使用场景就是在对象序列化时, 以 pack_object_visitor 作为参数, 调用为每个成员变量生成的重载方法.

witness_object 的反射代码

下面我们仍以 witness_object 为例, 不使用预编译器, 直接人工展开所有宏以生成 witness_object 的反射代码, witness_object 的宏调用在 <chain>/witness_object 最后:

// 代码 4.7

 73 FC_REFLECT_DERIVED( graphene::chain::witness_object, (graphene::db::object),
 74                     (witness_account)
 75                     (last_aslot)
 76                     (signing_key)
 77                     (pay_vb)
 78                     (vote_id)
 79                     (total_votes)
 80                     (url)
 81                     (total_missed)
 82                     (last_confirmed_block_num)
 83                   )

展开后生成的代码如下:

// 代码 4.8

namespace fc {  \
  template<> struct get_typename<graphene::chain::witness_object>  { static const char* name()  { return graphene::chain::witness_object;  } }; \
template<> struct reflector<graphene::chain::witness_object> {\
    typedef graphene::chain::witness_object type; \
    typedef fc::true_type  is_defined; \
    typedef fc::false_type is_enum; \
    enum  member_count_enum {  \
      local_member_count = 0  + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1,\
      total_member_count = local_member_count + 1 \
    }; \

    fc::reflector<graphene::db::object>::visit( visitor );

{ typedef decltype(((graphene::chain::witness_object*)nullptr)->witness_account) member_type;  \
  visitor.TEMPLATE operator()<member_type,graphene::chain::witness_object,&graphene::chain::witness_object::witness_account>( witness_account ); \
}

{ typedef decltype(((graphene::chain::witness_object*)nullptr)->last_aslot) member_type;  \
  visitor.TEMPLATE operator()<member_type,graphene::chain::witness_object,&graphene::chain::witness_object::last_aslot>( last_aslot ); \
}

{ typedef decltype(((graphene::chain::witness_object*)nullptr)->signing_key) member_type;  \
  visitor.TEMPLATE operator()<member_type,graphene::chain::witness_object,&graphene::chain::witness_object::signing_key>( signing_key ); \
}

//  剩下的 pay_vb, vote_id, total_votes 等成员的展开结果省略… 可类比推导…

}; }

后记

以上就是 graphene 里的反射模型了, 相比 Java, C# 语言中标准库自带的反射库, graphene 反射的信息很少, 只有类型名, 是否定义, 是否是枚举, 成员个数这些信息, 以及对对象成员操作的 visitor 方法, 当然这也是因为 graphene 的代码里只需要用到这些信息, 无需定义太多.

好了, 到本篇为止, 数据库篇的底层基础我们就讨论完了, 实际上还有一个大麻烦 fc::static_variant, 这个在后面 交易 篇章讨论 operations 的时候再说吧, 至少目前了解的这些已经足够看上层关于数据库部分的代码了, 从下一章开始我们就着手浏览应用层代码.

那么敬请期待下一章: 对象索引库

H2
H3
H4
3 columns
2 columns
1 column
2 Comments