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 <google\protobuf\message.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函數應該不是很困難,這里就不給出代碼了。
