目录

C++操作protobuf

我们用google/protobuf/struct.proto来研究cpp如何操作protobuf,然后用一个例子验证我们的理解。

首先看google/protobuf/struct.proto的结构:

message Struct {
  // Unordered map of dynamically typed values.
  map<string, Value> fields = 1;
}

// Represents a JSON value.
//
// `Value` represents a dynamically typed value which can be either
// null, a number, a string, a boolean, a recursive struct value, or a
// list of values. A producer of value is expected to set one of these
// variants. Absence of any variant is an invalid state.
message Value {
  // The kind of value.
  oneof kind {
    // Represents a JSON `null`.
    NullValue null_value = 1;

    // Represents a JSON number. Must not be `NaN`, `Infinity` or
    // `-Infinity`, since those are not supported in JSON. This also cannot
    // represent large Int64 values, since JSON format generally does not
    // support them in its number type.
    double number_value = 2;

    // Represents a JSON string.
    string string_value = 3;

    // Represents a JSON boolean (`true` or `false` literal in JSON).
    bool bool_value = 4;

    // Represents a JSON object.
    Struct struct_value = 5;

    // Represents a JSON array.
    ListValue list_value = 6;
  }
}

// Represents a JSON `null`.
//
// `NullValue` is a sentinel, using an enum with only one value to represent
// the null value for the `Value` type union.
//
// A field of type `NullValue` with any value other than `0` is considered
// invalid. Most ProtoJSON serializers will emit a `Value` with a `null_value`
// set as a JSON `null` regardless of the integer value, and so will round trip
// to a `0` value.
enum NullValue {
  // Null value.
  NULL_VALUE = 0;
}

// Represents a JSON array.
message ListValue {
  // Repeated field of dynamically typed values.
  repeated Value values = 1;
}

我们最终会创造一个结构如下的Struct

{
  "name": "Alice",
  "age": 30,
  "is_admin": true,
  "nothing": null,

  "address": {
    "city": "Seattle",
    "zip": 98052
  },

  "languages": [
    "C++",
    "Go",
    "Python"
  ]
}

C++操作总结

Proto fieldC++ 写C++ 读
stringset_xxx()xxx()
int32set_xxx()xxx()
int64set_xxx()xxx()
boolset_xxx()xxx()
enumset_xxx()xxx()
messagemutable_xxx() 返回指针xxx() 返回常指针
repeated scalaradd_xxx()xxx(), xxx(int) / range-for
repeated messageadd_xxx() 返回指针xxx() 返回 RepeatedPtrField常指针, xxx(int) 返回常指针 / range-for
mapmutable_xxx()xxx()
oneofset_xxx()mutable_xxx()xxx() + xxx_case() (返回case类型字符串常量)

mutable_XXX()是什么?

mutable_XXX是protobuf C++ 里非常重要的一组 API。 假设:

message Person {
    Address address = 1;
}

C++ 通常会生成:

const Address& address() const;
Address* mutable_address();

所以,只读时用:

const Address& address = person.address();

修改时用:

Address* address = person.mutable_address();

C++操作

设置值(set values)

#include <iostream>
#include <fstream>
#include <string>

#include <google/protobuf/struct.pb.h>

using google::protobuf::ListValue;
using google::protobuf::NullValue;
using google::protobuf::Struct;
using google::protobuf::Value;

void SetData(Struct* data) {
    // Set string value.
    (*data->mutable_fields())["name"].set_string_value("Alice");

    // Set number value.
    (*data->mutable_fields())["age"].set_number_value(30);

    // Set bool value.
    (*data->mutable_fields())["is_admin"].set_bool_value(true);

    // Set NULL value.
    (*data->mutable_fields())["nothing"].set_null_value(
        NullValue::NULL_VALUE
    );

    // ------------------------------------------------------------
    // 5. nested Struct
    //
    // address:
    // {
    //     city: "Seattle",
    //     zip: 98052
    // }
    // ------------------------------------------------------------
    Struct* address =
        (*data->mutable_fields())["address"].mutable_struct_value();
    (*address->mutable_fields())["city"].set_string_value("Seattle");
    (*address->mutable_fields())["zip"].set_number_value(98052);

    // ------------------------------------------------------------
    // 6. ListValue
    //
    // languages:
    // [
    //     "C++",
    //     "Go",
    //     "Python"
    // ]
    // ------------------------------------------------------------
    ListValue* languages =
        (*data->mutable_fields())["languages"].mutable_list_value();
    languages->add_values()->set_string_value("C++");
    languages->add_values()->set_string_value("Go");
    languages->add_values()->set_string_value("Python");
}

读取值(read value)

void ReadData(const Struct& data) {
    // Read map.
    const auto& fields = data.fields();
    std::cout << "number of fields = "
              << fields.size()
              << "\n\n";

    // Read string value.
    const Value& name = fields.at("name");
    std::cout << "name = "
              << name.string_value()
              << "\n";

    // Read number value.
    const Value& age = fields.at("age");
    std::cout << "age = "
              << age.number_value()
              << "\n";


    // Read bool value.
    const Value& is_admin = fields.at("is_admin");
    std::cout << "is_admin = "
              << std::boolalpha
              << is_admin.bool_value()
              << "\n";


    // Read one of value.
    // Value::kind_case() 可以告诉我们:
    // 当前到底设置了哪一个 oneof field
    const Value& value = fields.at("name");
    switch (value.kind_case()) {
        case Value::kStringValue:
            std::cout << "name contains a string\n";
            break;
        case Value::kNumberValue:
            std::cout << "name contains a number\n";
            break;
        case Value::kBoolValue:
            std::cout << "name contains a bool\n";
            break;
        case Value::kNullValue:
            std::cout << "name contains null\n";
            break;
        case Value::kStructValue:
            std::cout << "name contains a struct\n";
            break;
        case Value::kListValue:
            std::cout << "name contains a list\n";
            break;
        case Value::KIND_NOT_SET:
            std::cout << "name contains nothing\n";
            break;
    }

    // Read nested proto field.
    const Value& address_value = fields.at("address");
    const Struct& address = address_value.struct_value();
    std::cout << "\naddress.city = "
              << address.fields().at("city").string_value()
              << "\n";
    std::cout << "address.zip = "
              << address.fields().at("zip").number_value()
              << "\n";


    // Read repeated/ListValue value.
    const Value& languages_value = fields.at("languages");
    const ListValue& languages =
        languages_value.list_value();
    std::cout << "\nlanguages:\n";
    for (const Value& language : languages.values()) {
        std::cout << "  - "
                  << language.string_value()
                  << "\n";
    }
}

序列化和反序列化(Serialize and de-serialize)

    // Serialize
    std::string serialized;
    if (!data.SerializeToString(&serialized)) {
        std::cerr << "Serialize failed\n";
    }
    std::cout << "\nserialized size = "
              << serialized.size()
              << " bytes\n";

    // Parse
    Struct parsed;
    if (!parsed.ParseFromString(serialized)) {
        std::cerr << "Parse failed\n";
    }

生命周期

nested message 的生命周期由 protobuf parent object 管理。 这是 protobuf C++ 和普通 C++ object ownership 一个很重要的区别。

另一个例子

参考文档 以另一个proto为例:

edition = "2023";

package tutorial;

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;

  enum PhoneType {
    PHONE_TYPE_UNSPECIFIED = 0;
    PHONE_TYPE_MOBILE = 1;
    PHONE_TYPE_HOME = 2;
    PHONE_TYPE_WORK = 3;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

我们可以看到生成的C++ API如下:

  // name
  bool has_name() const; // Only for explicit presence
  void clear_name();
  const ::std::string& name() const;
  void set_name(const ::std::string& value);
  ::std::string* mutable_name();

  // id
  bool has_id() const;
  void clear_id();
  int32_t id() const;
  void set_id(int32_t value);

  // email
  bool has_email() const;
  void clear_email();
  const ::std::string& email() const;
  void set_email(const ::std::string& value);
  ::std::string* mutable_email();

  // phones
  int phones_size() const;
  void clear_phones();
  const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phones() const;
  ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phones();
  const ::tutorial::Person_PhoneNumber& phones(int index) const;
  ::tutorial::Person_PhoneNumber* mutable_phones(int index);
  ::tutorial::Person_PhoneNumber* add_phones();