For protocol buffers in C++, I am wondering if it is better to contain a protobuf message in my class, or to have it be constructed from and populate an external protobuf message.
I could not find examples describing best practices for this case. I'm particular worried about performance differences between the two designs.
In my processing, I will have some cases where I am going to read only a few fields from my message and then route the message to another process (possibly manipulating the message before sendind it back out), and other cases where my objects will have a long lifetime and be used many times before being serialized again. In the first case, I could likely operate directly on the protobuf message and not even need my class, execpt to fit into an existing interface.
Here is an example message:
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;
}
I could see one of the following designs for my class. I know these classes aren't doing anything else but accessing data, but that's not what I'm trying to focus on for this question.
Composition
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_;
};
Standard data members
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_;
};
 
    