3

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_;
};

korhadris
  • 157
  • 6
  • 1
    Most answers to this question are going to be opinion-based. In any case, another consideration is validation and security. If you construct a class directly from a buffer, you'll need to do this yourself (assuming the buffer can come from an untrusted source), whereas if all you do is hand it off to another component, you can let the consuming component worry about it. – MooseBoys May 14 '15 at 20:55
  • Almost all questions that use the words "best practices" are likely to be considered opinion-based. – Barmar May 14 '15 at 21:06
  • I figure I'm leaving it up to protocol buffers to deal with the construction from raw buffers. Or should I be more concerned that protocol buffers are inherently insecure? – korhadris May 14 '15 at 21:11
  • 1
    Use protocol buffers to define your communication protocol. Don't let that influence your object design. Translate exactly at the point where you send or receive. – Alan Stokes May 14 '15 at 21:33
  • @AlanStokes I'm hoping that's what I should do. I spent a lot of time working the first example and thought it was getting to be too much. I did sorta like the idea of reusing the message as a single container to store all my class data. – korhadris May 14 '15 at 21:48
  • That works up to the point where you want to change the class data without breaking the other users of the message. – Alan Stokes May 15 '15 at 11:20

1 Answers1

1

There is no recognized "best practice" here. I have seen plenty of examples of both, and even written programs that worked both ways. Some people have very strong opinions about this, but in my opinion it depends on the use case. For example, as you say, if you plan to forward most of the data to another server, then it makes a lot of sense to keep the protobuf object around. But other times you have a more convenient internal representation -- for example, before protobufs added native support for maps, if you had a protobuf that represented a map as a repeated list of key/value pairs, you might want to convert it to an std::map upfront.

Kenton Varda
  • 41,353
  • 8
  • 121
  • 105
  • Knowing all you've done with GPB and Cap'n Proto, it's good to know that there are both cases out there. I was originally worried I was way off track here. Thanks. – korhadris May 15 '15 at 21:44