使用反射處理protobuf數據結構


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM