Graphene 源码阅读 ~ 数据库篇 ~ 对象序列化

虽然 graphene 的代码我还没看完, 但我敢说 <fc>/io/raw.hpp 肯定能算得上 graphene 代码中最难读的部分之一. 实际上 fc 整体都是比 graphene 其它部分的代码要难读很多的 (见 源码结构).

<fc>/io/raw.hpp 对 C++ 模板的运用可谓无所不用其极, 说是达到了丧心病狂的地步可能也不为过, 单单一个 pack() 方法, <fc>/io/raw.hpp 中为其定义了 40 套模板定义!!! 注意, 不是同一模板的 40 个实例, 是 40 套模板定义!!! 所以, 在看序列化部分的代码时, 需要仔细判别每个调用匹配的是哪个模板.

好了, 下面开始剖析代码, 追踪一下整个序列化过程, 然后进行回溯.

序列化过程追踪

序列化过程的开始得从上一章 索引模型 中提到过的 index::save() 方法说起, 因为它就是把内存中的索引结构存磁盘的方法嘛. 这个方法的实现在 primary_index 类,

// 代码 3.1

virtual void save( const path& db ) override
{
    std::ofstream out( db.generic_string(),
           std::ofstream::binary | std::ofstream::out | std::ofstream::trunc );
    FC_ASSERT( out );
    auto ver  = get_object_version();
    fc::raw::pack( out, _next_id );
    fc::raw::pack( out, ver );
    this->inspect_all_objects( [&]( const object& o ) {
        auto vec = fc::raw::pack( static_cast<const object_type&>(o) );
        auto packed_vec = fc::raw::pack( vec );
        out.write( packed_vec.data(), packed_vec.size() );
    });
}

这里传进来一个 db 参数, 它指的是落盘路径, 然后一个 out 文件流打开, 意味着后续此索引的序列化结果都将写入这个 out 流. 首先 _next_id 和版本信息被序列化了并写入流了, 这里就调用了 fc::raw::pack( out, _next_id );, 这个就需要仔细追踪实例的是哪个模板方法. 我们关心的是索引中对象的序列化, 所以我们用 inspect_all_objects() 部分来演绎一下这个追踪过程.

inspect_all_objects() 方法在上一章也提到过, 这里它接受一个 lambda 方法对每个对象进行序列化打包操作然后写入 out 流. 这里调用了两次 fc::raw::pack() 方法, 两次调用追踪的原理是一样的, 我们只需来一起好好追踪一下第一次调用.

第一次 fc::raw::pack 调用, 参数类型很明确, 是 const object_type& 类型, 这个 object_type 是实例化 primary_index 时决定的, 是 db::object 的子类, 比如是我们前几篇文章一直拿来举例的 witness_object. 经过仔细比对, 我们发现这次调用实例化的模板是 <fc>/io/raw.hpp 第 545 行左右定义的那个模板, 它是唯一满足这次调用的模板方法: 只接受一个参数, 并且参数类型是引用类型.

// 代码 3.2

544     template<typename T>
545     inline std::vector<char> pack(  const T& v ) {
546       datastream<size_t> ps;
547       fc::raw::pack(ps,v );
548       std::vector<char> vec(ps.tellp());
549
550       if( vec.size() ) {
551         datastream<char*>  ds( vec.data(), size_t(vec.size()) );
552         fc::raw::pack(ds,v);
553       }
554       return vec;
555     }

然而麻烦的事情在后面, 这个模板中接着又调用了两次 fc::raw::pack(), 同理, 我们只追踪第一次 fc::raw::pack(ps, v);, 这个调用形式匹配的是 527 行定义的模板:

// 代码 3.3

526     template<typename Stream, typename T>
527     inline void pack( Stream& s, const T& v ) {
528       fc::raw::detail::if_reflected< typename fc::reflector<T>::is_defined >::pack(s,v);
529     }

好, 这里定义的模板调用挺长, 但不要被吓到, 先说明一下 fc::reflector<T> 是在每个对象的定义中由 FC_REFLECT 宏定义的, 这部分后续我们讨论对象反射时会说到, 这里我们只要知道 fc::reflector<T>::is_defined 这一串的值是 fc::true_type 就可以了.

于是上面 528 行的 pack() 调用就匹配到了定义于 344 行的模板:

// 代码 3.4

344       template<typename IsReflected=fc::false_type>
345       struct if_reflected {
346         template<typename Stream, typename T>
347         static inline void pack( Stream& s, const T& v ) {
348           if_class<typename fc::is_class<T>::type>::pack(s,v);
349         }
350         template<typename Stream, typename T>
351         static inline void unpack( Stream& s, T& v ) {
352           if_class<typename fc::is_class<T>::type>::unpack(s,v);
353         }
354       };
355       template<>
356       struct if_reflected<fc::true_type> {
357         template<typename Stream, typename T>
358         static inline void pack( Stream& s, const T& v ) {
359           if_enum< typename fc::reflector<T>::is_enum >::pack(s,v);
360         }
361         template<typename Stream, typename T>
362         static inline void unpack( Stream& s, T& v ) {
363           if_enum< typename fc::reflector<T>::is_enum >::unpack(s,v);
364         }
365       };

因为我们已经知道 fc::reflector<T>::is_defined 的值是 fc::true_type, 所以这里实际上直接匹配了 356 行的模板. 至于 356 行的模板定义如果你是第一次见这种写法, 可以就去翻一翻 C++ primer 了解一下什么是 模板特化.

再然后我们匹配到了 359 行, 相似的, 我可以直接告诉这里 fc::reflector<T>::is_enum 的值就是 fc::false_type, 于是我们又匹配到了 319 行的定义处:

// 代码 3.5

319       template<typename IsEnum=fc::false_type>
320       struct if_enum {
321         template<typename Stream, typename T>
322         static inline void pack( Stream& s, const T& v ) {
323           fc::reflector<T>::visit( pack_object_visitor<Stream,T>( v, s ) );
324         }
325         template<typename Stream, typename T>
326         static inline void unpack( Stream& s, T& v ) {
327           fc::reflector<T>::visit( unpack_object_visitor<Stream,T>( v, s ) );
328         }
329       };
330       template<>
331       struct if_enum<fc::true_type> {
332         template<typename Stream, typename T>
333         static inline void pack( Stream& s, const T& v ) {
334           fc::raw::pack(s, (int64_t)v);
335         }
336         template<typename Stream, typename T>
337         static inline void unpack( Stream& s, T& v ) {
338           int64_t temp;
339           fc::raw::unpack(s, temp);
340           v = (T)temp;
341         }
342       };

这次你应该知道匹配的是 322 行那个模板了, 于是我们要再看看 fc::reflector<T>::visit( pack_object_visitor<Stream,T>( v, s ) ); 是什么鬼东西. fc::reflector<T>::visit() 会在下文做简短的说明, 后续讨论对象反射时详细说. 现在只需要知道这个方法会调用传入的 pack_object_visitor 就可以了, pack_object_visitor 的定义在 271 行, 有 pack_object_visitor 对应的也就有 unpack_object_visitor:

// 代码 3.6

270       template<typename Stream, typename Class>
271       struct pack_object_visitor {
272         pack_object_visitor(const Class& _c, Stream& _s)
273         :c(_c),s(_s){}
274
275         template<typename T, typename C, T(C::*p)>
276         void operator()( const char* name )const {
277           fc::raw::pack( s, c.*p );
278         }
279         private:
280           const Class& c;
281           Stream&      s;
282       };
283
284       template<typename Stream, typename Class>
285       struct unpack_object_visitor {
286         unpack_object_visitor(Class& _c, Stream& _s)
287         :c(_c),s(_s){}

可以看到 pack_object_visitor 是重载了 operator () 方法的, fc::reflector<T>::visit() 在调用 pack_object_visitor 时会调到了这个重载方法里. operator () 重载操作函数也是模板函数, 这里有必要解释一下这个重载函数中的 T(C::*p) 参数,

graphene 的 visitor 模型中, 每个 visitor (这里是 pack_object_visitor) 都要重载 operator() 操作符, 并且其模板参数就如 代码 3.6 里定义的一样. fc::reflector<T>::visit() 方法在实际调用 pack_object_visitor 时是以类似如下方式调用的:

// 代码 3.7  (取自 /object_id.hpp, 这是访问 object_id 类的 “instance” 成员的一个特例)

163     template<typename Visitor>
164     static inline void visit( const Visitor& visitor )
165     {
166        typedef decltype(((type*)nullptr)->instance) member_type;
167        visitor.TEMPLATE operator()<member_type,type,&type::instance>( "instance" );
168     }

所以三个模板参数, 分别是对象成员的类型, 对象的类型, 以及对象成员的地址. 于是代码 3.6 中的三个模板以及就可以理解了. 代码 3.7 只是访问 object_id 对象的 instance 成员的一个特例, 在实际访问如 witness_object 对象时, witness_object 中的每个成员都会被访问一遍.

接下来, 我们继续向下一站进发找出下一个匹配的 pack() 模板. 从代码 3.6 的定义可以得知, fc::raw::pack( s, c.*p ); 中的 sc 分别就是 datastream<size_t>object(如 witness_object), c.*p 如上所述就是对象的某个特定参数了. 以 witness_object 对象的 url 成员为例, 它是一个 int64_t 类型的成员, 于是我们终于到达了最终站 (signed_intfc 定义的整型类, 能接受 int64_t, int32_t 等各种长度的整型):

// 代码 3.8

146     template<typename Stream> inline void pack( Stream& s, const signed_int& v ) {
147       uint32_t val = (v.value<<1) ^ (v.value>>31);
148       do {
149         uint8_t b = uint8_t(val) & 0x7f;
150         val >>= 7;
151         b |= ((val > 0) << 7);
152         s.write((char*)&b,1);//.put(b);
153       } while( val );
154     }

可以看到对整型的序列化就是一些移位, 逻辑与之类的操作. 对其它类型成员的序列化如 string, vector 可以自行看 fc::raw 的代码, 这里不再敖述.

回溯

在 代码 3.8, 3.7, 以及 3.6 处, 每一个对象的成员都被用各种序列化方法写入了 datastream<> 模板类; 然后一路返回到 代码 3.5, 3.4, 以及 3.3 处, Stream& s 由于是个引用所以当然也能得到写入的数据; 然后再返回到代码 3.2 处, 序列化好的数据被存入一个 vector 中返回给 代码 3.1, 代码 3.1 处看起来会对这个 vector 再次序列化, 然后最终写入 out 流.

至此, 对象的序列化终于算追溯完成了.

相应的, 对象的反序列化过程对应的就是由 index::open(), index::load() 所触发的过程, 过程类似, 有兴趣的话可以自行追溯一下.

后记

原本打算对象序列化和反射一起写, 但写时发现由于这俩代码都比较复杂, 可能因为都是 fc 的代码吧, 写一起篇幅会比较长, 所以这篇还是只写了序列化.

最后发表点感慨…

fc 部分的代码, 真是充分展示出了 BM 深厚的 C++ 模板把玩功底, fc::raw 部分的代码我已经觉得丧心病狂了, 结果后来看到 fc::static_variant 把模板玩出递归时, 真是感觉再也不想碰 C++ 了… 以后我要是写自己的区块链一定要用 golang 写 :P

H2
H3
H4
3 columns
2 columns
1 column
9 Comments