google的protobuf是一种轻便高效的结构化数据存储格式,在通信协议和数据存储等领域中使用比较多。protobuf对于结构中的每个成员,会提供set系列函数和get系列函数。
但是,对于使用来说,需要根据传入的参数考虑需要调用的函数名,在使用这个比较多的情况,还是会让人觉得有些麻烦。而且,对于有些使用,例如之后打算不再使用protobuf,改为直接将数据压入内存段(The raw in-memory data structures sent/saved in binary form),或者直接压入内存段的方式,改为使用protobuf,那么,如果两者都是通过传入函数的方式来进行数据设置,或者数据解析,那么改动内容会比较少,而且出错几率也会比较低。那么如何实现呢?
先给出proto文件:
syntax = "proto2"; package student; message Student { required string name = 1; required int32 id = 2; optional int32 age = 3; optional string phoneNumber = 4; }
下面给出通过函数重载方式,来处理各种参数类型的方式:
#include <googleprotobufmessage.h> namespace goo_proto = ::google::protobuf; template <typename Param> struct ProtoFunc { static constexpr void* SetValueFunc = nullptr; }; template <> struct ProtoFunc<goo_proto::int32> { static constexpr auto SetValueFunc = &(goo_proto::Reflection::SetInt32); }; template <> struct ProtoFunc<goo_proto::int64> { static constexpr auto SetValueFunc = &(goo_proto::Reflection::SetInt64); }; template <> struct ProtoFunc<std::string> { static constexpr auto SetValueFunc = &(goo_proto::Reflection::SetString); }; template <> struct ProtoFunc<const char*> { static constexpr auto SetValueFunc = &(goo_proto::Reflection::SetString); }; template <typename ValueType> void SetFieldValue(goo_proto::Message* msg, const goo_proto::Reflection* reflection, const goo_proto::FieldDescriptor* field, ValueType&& value) { (reflection->*(ProtoFunc<std::remove_cv_t<std::decay_t<ValueType>>>::SetValueFunc)) (msg, field, std::forward<ValueType>(value)); }
通过上述的模板,就可以调用SetFieldValue来处理int32, int64, std::string和const char*了,这个是实现泛型函数的前期准备。
下面给出一次设置多个参数的方式:
template <typename ...Args> void SetFieldAllValues(goo_proto::Message* msg, Args&&... args) { auto descriptor = msg->GetDescriptor(); auto reflection = msg->GetReflection(); SetFieldImpl<0>(msg, descriptor, reflection, std::forward<Args>(args)...); } template <size_t idx, typename T, typename ...Args> void SetFieldImpl(goo_proto::Message* msg, const goo_proto::Descriptor* descriptor, const goo_proto::Reflection* reflection, T&& value, Args&&... args) { auto field = descriptor->field(idx); SetFieldValue(msg, reflection, field, std::forward<T>(value)); SetFieldImpl<idx + 1>(msg, descriptor, reflection, std::forward<Args>(args)...); } template <size_t idx> void SetFieldImpl(goo_proto::Message* msg, const goo_proto::Descriptor* descriptor, const goo_proto::Reflection* reflection) { // empty }
上面就是实现,设置所有proto结构体中元素的方式。多谢protobuf中提供了descriptor::field函数,通过这个函数,我才有办法比较简单的实现通过传入所有的参数(这里没有考虑设置repeat成员),来一次性设置好整个proto结构体对象。下面看一下使用上述函数的一个实例:
#include <iostream> #include <string> #include "studentinfo.h" #include "studentinfo.pb.h" int main(int argc, char* argv[]) { student::Student s; SetFieldAllValues(&s, "Jack", 10, 20, "11122233345"); std::cout << s.name() << std::endl; std::cout << s.id() << std::endl; std::cout << s.age() << std::endl; std::cout << s.phonenumber() << std::endl; return 0; }
这里只是提供了设置的函数(Set函数),没有提供Get函数,不过根据类似的方式,实现Get函数应该不是很困难,这里就不给出代码了。