C++ 类 应该包含协议缓冲区消息还是 constructed/populated 来自协议缓冲区消息

Should C++ classes contain or be constructed/populated from protocol buffer messages

对于 C++ 中的协议缓冲区,我想知道是在我的 class 中包含一个 protobuf 消息更好,还是让它从外部 protobuf 消息构造并填充它更好。

我找不到描述此案例最佳实践的示例。我特别担心两种设计之间的性能差异。

在我的处理中,我会遇到一些情况,我将只从我的消息中读取几个字段,然后将消息路由到另一个进程(可能在将消息发送回之前对消息进行操作),以及其他情况我的对象将有很长的生命周期,并且在再次序列化之前会被多次使用。在第一种情况下,我可能会直接对 protobuf 消息进行操作,甚至不需要我的 class、execpt 来适应现有接口。

这是一个示例消息:

package example;
message Example {
  optional string name = 1;
  optional uint32 source = 2;
  optional uint32 destination = 3;
  optional uint32 value_1 = 4;
  optional uint32 value_2 = 5;
  optional uint32 value_3 = 6;
}

我的 class 可以看到以下设计之一。我知道这些 classes 除了访问数据之外没有做任何其他事情,但这不是我在这个问题上想要关注的。


作文

class Widget
{
 public:
  Widget() : message_() {}
  Widget(const example::Example& other_message)
   : message_(other_message) {}


  const example::Example& getMessage() const
  { return message_; }

  void populateMessage(example::Example& message) const
  { message = message_; }

  // Some example inspectors filled out...
  std::string getName() const
  { return message_.name(); }

  uint32_t getSource() const;
  { return message_.source(); }

  uint32_t getDestination() const;
  uint32_t getValue1() const;
  uint32_t getValue2() const;
  uint32_t getValue3() const;

  // Some example mutators filled out...
  void setName(const std::string& new_name)
  { message_.set_name(new_name); }

  void setSource(uint32_t new_source);
  { message_.set_source(new_source); }

  void setDestination(uint32_t new_destination);
  void setValue1(uint32_t new_value);
  void setValue2(uint32_t new_value);
  void setValue3(uint32_t new_value);

 private:
  example::Example message_;
};

标准数据成员

class Widget
{
 public:
  Widget();
  Widget(const example::Example& other_message)
   : name_(other_message.name()),
     source_(other_message.source()),
     destination_(other_message.destination()),
     value_1_(other_messsage.value_1()),
     value_2_(other_messsage.value_2()),
     value_3_(other_messsage.value_3())
  {}

  example::Example getMessage() const
  {
    example::Example message;
    populateMessage(message);
    return message;
  }

  void populateMessage(example::Example& message) const
  {
    message.set_name(name_);
    message.set_source(source_);
    message.set_value_1(value_1_);
    message.set_value_2(value_2_);
    message.set_value_3(value_3_);
  }

  // Some example inspectors filled out...
  std::string getName() const
  { return name_; }

  uint32_t getSource() const;
  { return source_; }

  uint32_t getDestination() const;
  uint32_t getValue1() const;
  uint32_t getValue2() const;
  uint32_t getValue3() const;

  // Some example mutators filled out...
  void setName(const std::string& new_name)
  { name_ = new_name; }

  void setSource(uint32_t new_source);
  { source_ = new_source; }

  void setDestination(uint32_t new_destination);
  void setValue1(uint32_t new_value);
  void setValue2(uint32_t new_value);
  void setValue3(uint32_t new_value);

 private:
  std::string name_;
  uint32_t source_;
  uint32_t destination_;
  uint32_t value_1_;
  uint32_t value_2_;
  uint32_t value_3_;
};

这里没有可识别的"best practice"。我已经看到了很多这两种方式的例子,甚至看到了两种方式都有效的编写程序。有些人对此有很强的看法,但我认为这取决于用例。例如,正如您所说,如果您计划将大部分数据转发到另一台服务器,那么保留 protobuf 对象就很有意义。但其他时候你有更方便的内部表示——例如,在 protobufs 添加对地图的本地支持之前,如果你有一个将地图表示为 key/value 对的重复列表的 protobuf,你可能想要转换它提前 std::map