虽然 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 );
中的 s
和 c
分别就是 datastream<size_t>
和 object
(如 witness_object), c.*p
如上所述就是对象的某个特定参数了. 以 witness_object
对象的 url
成员为例, 它是一个 int64_t
类型的成员, 于是我们终于到达了最终站 (signed_int
是 fc
定义的整型类, 能接受 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