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

in bitshares •  9 months ago

虽然 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  (取自 <db>/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

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  
·

感谢 A 神

事前に感謝して...待ってupvote戻ってきた。 @cifer

事前に感謝して...待ってupvote.....

The writers like you need the steemit community. By doing so you can succeed in providing us very good information. I am proud that I am part of this society. I read every article you need. And indeed I responded very much to you and your articles.

I know you can climb the highest mountains. Never lose faith in yourself. Good luck!

All the best for a bright future! May there be success at every turn of life and all your dreams come true!

什么也看不懂,就知道 点赞就对了哈哈哈

·

哈哈哈 感谢一姐~

Congratulations @cifer, this post is the sixth most rewarded post (based on pending payouts) in the last 12 hours written by a User account holder (accounts that hold between 0.1 and 1.0 Mega Vests). The total number of posts by User account holders during this period was 2734 and the total pending payments to posts in this category was $5634.90. To see the full list of highest paid posts across all accounts categories, click here.

If you do not wish to receive these messages in future, please reply stop to this comment.

好几年没碰c++,现在又重新捡起来,累