虽然 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 );
中的 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
什么也看不懂,就知道 点赞就对了哈哈哈
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
哈哈哈 感谢一姐~
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
深度好文。
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
感谢 A 神
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
事前に感謝して...待ってupvote戻ってきた。 @cifer
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
事前に感謝して...待ってupvote.....
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
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!
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
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.
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
好几年没碰c++,现在又重新捡起来,累
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit