Repeated类型
field包含2种类型:
- Strings/Message类型使用RepeatedPtrFields
- enum / primitive类型使用RepeatedFields
核心类关系图如下:
类RepeatedField
模板类,为primitive类型数据提供repeated类型容器。内部实现为连续内存的array(保存的内容就是primitive对象),并且对外提供iterator方式来访问。
array起始大小为4(static const int kInitialSize = 4;),当长度不够的时候会分配新的内存(max(total_size_ * 2, new_size))以及内存拷贝,所以如果repeated的成员很多,使用Reserve()接口能节省array增大时的内存分配和数据拷贝成本。
类RepeatedPtrFieldBase
RepeatedPtrFields的父类(不是模板类,提供了多个模板函数),本身保存/管理的数据类型为void*(message对象的实际地址,也是通过连续内存array来保存)。
RepeatedPtrFieldBase类并不感知自己管理的具体是什么message,通过模板函数的模板参数TypeHandler来为各种数据类型数据提供服务,例如:
1 | template <typename TypeHandler> |
因为array中保存的是同一个descriptor对应的message,只是各个message中所包含的数据不一样,为了节省下message对象分配/删除的成本,所以message可以被clear(clear操作会将primitive类型的field设置为0,其余类型field调用自身的clear()接口处理,例如string类型的std::string::clear(),只清理数据并不回收内存),然后保留原有的内存地址在array中。下次需要从array中分配message时,优先使用这一批被clear的message(实现在RepeatedPtrFieldBase::AddFromCleared() ,参考GeneratedMessageReflection::AddMessage()中的调用方式)。
为了管理cleared状态的message指针,引入了多个游标来标记数据:
- current_size_: 当前待处理的message地址;
- allocated_size_:已经分配message的数据,current_size_ <= allocated_size_,从current_size_到allocated_size_之间的message就是被cleared的;
- total_size_: elements_[]的长度,但从allocated_size_到total_size_之间的void*是无效的,并没有指向任何message;
对应内存分布如下:
类RepeatedPtrField
模板类,RepeatedPtrFieldBase的子类,为Strings/Message类型数据提供repeated类型容器。
Q: 这里RepeatedPtrField类是RepeatedPtrFieldBase类唯一的子类,是否也没有必要这样区分父类/子类呢?
Answer如下:
提前铺垫(父类/子类的分工):
- RepeatedPtrFieldBase(非模板类,提供模板函数)负责的是最基本的基于array<void*>的操作,并不感知所保存的内容的数据类型,所有需要区分类型的操作都有模板类型TypeHandler来负责;
- RepeatedPtrField(模板类)感知数据类型(数据类型由模板参数Element提供),并且对外的接口都是基于类型Element的。针对Element的操作则由TypeHandler来负责,并且通过父类RepeatedPtrFieldBase模板函数的参数传递给父类。
这种分工可以在RepeatedPtrField
1 | template <typename Element> |
本质原因:
某些情况下无法感知子类(模板类)RepeatedPtrField
例如在GeneratedMessageReflection::AddMessage()中,其实message子类中保存的是RepeatedPtrField
- 调用RepeatedPtrFieldBase::AddFromCleared
(),尝试获取已cleared但未释放的message对象。如果没有,就继续; - 获取一个prototype(指向真实Message子类对象的父类Message指针):
(2.1)先看RepeatedPtrFieldBase的array<RepeatedPtrField>是否有成员,如果有就使用;
(2.2)调用factory->GetPrototype()创建一个; - 调用prototype(指向真实Message子类对象的父类Message指针)的Message::New()接口,创建出一个真实field_descriptor对应的Message子类对象;
1 | Message* GeneratedMessageReflection::AddMessage( |
Q:这里的TypeHandler是在哪里定义的呢?类RepeatedPtrField中并没有提供接口来针对不同数据类型设置typehandler?
Answer如下:
定义在repeated_field.h中,根据模板类RepeatedPtrField<>模板参数的不同(Element或者string),继承了不同的父类(因为这里子类自己并没有独有的数据/行为,所以用这种方式来选择使用哪种handler):
1 | template <typename Element> |
typehandler是直接子类RepeatedPtrField在调用父类RepeatedPtrFieldBase的模板函数时,通过模板参数直接传入父类RepeatedPtrFieldBase,可以通过GeneratedMessageReflection中使用的例子来看:
1 | const Message& GeneratedMessageReflection::GetRepeatedMessage( |
模板函数RepeatedPtrFieldBase::Get
1 | template <typename TypeHandler> |
注意:这里TypeHandler是RepeatedPtrField类的protected成员,为了不让用户再将RepeatedPtrField作为父类来使用:
1 | protected: |
类GenericTypeHandler
针对message的typehandler
类StringTypeHandler
StringTypeHandlerBase的子类,在父类基础上增加了SpaceUsed()接口。
1 | class LIBPROTOBUF_EXPORT StringTypeHandler : public StringTypeHandlerBase { |
这里需要理解string的数据结构来理解这段代码了,从代码看start/end是保存在string对象的前第一/前第二个位置void*
(sizeof(void*)为8个byte)的。
1 | int StringSpaceUsedExcludingSelf(const string& str) { |
Q: 为什么需要区分父类/子类呢?直接使用StringTypeHandler即可啊
Answer如下:
1 | // HACK: If a class is declared as DLL-exported in MSVC, it insists on |