Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Derive from std:string to add typedefs and enums

For UDP packages exchanged between client and server I would like to support two kinds of string fields:

  • null-terminated c-string named cstring
  • string with preceeding uint8_t size-field named vstring

To self-document the layout of our packages I would like to use simple struct declarations:

struct ABC {
  vstring a;
  cstring b;
}

And call overloaded functions get(char*, vstring& v) and get(char*, cstring&) inside de/serialization function like this:

void deserialize(const char* bytes, ABC& msg) {
  get(msg.a);
  get(msg.b);
}

void serialize(char* bytes, const ABC& msg) {
  put(msg.a);
  put(msg.b);
}

However, for the user vstring and cstring should ideally behave just like normal std::string.

My first idea was to simply make std::string a public base of vstring and cstring such that the two classes can be disriminated during overload-resolution but behave the same for the user. But since deriving from std::string is discouraged, I am unsure what to do.

like image 266
Arne Avatar asked Apr 27 '15 08:04

Arne


3 Answers

The danger of deriving from std::string is that the destructor is not virtual, so someone might do this:

std::string* p = new vstring;
delete p;

You'd have undefined behaviour. If you think that code like that has no chance of being written in your environment/system, knock yourself out and derive from std::string.

Guidelines:

  • If the vstring and cstring classes are only being used in a very limited, controlled environment - preferably by one or a small number of developers with whom you can communicated the expected - and monitor the actual - usage, or where it's clear that dynamic allocation and polymorphism won't be abused to handle them, all's good.

  • At the other extreme - if they're in your interfaces to unspecified amounts of uncontrolled code, where it's not even practical to inform all the would-be client developers of the issue - that's not good, and a scenario in which you should prefer not to derive from types without virtual destructors.


That said, do you really need to encode the serialisation style in the type? They have to write serialisation and deserialisation routines anyway, which list the fields to be serialised, and they could specify the format (NUL-terminated vs. length-prefixed or whatever else) in there too.

like image 104
Tony Delroy Avatar answered Nov 10 '22 12:11

Tony Delroy


Ideologically, the way of serialization shouldn't affect the type of data, so I'd better do something like this:

void serializeNullTerminated(char* bytes, const std::string& msg);
void deserializeNullTerminated(const char* bytes, std::string& msg);

void serializeWithSize(char* bytes, const std::string& msg);
void deserializeWithSize(const char* bytes, std::string& msg);

Or pass an additional parameter to the functions:

void serialize(SerializationType st, char* bytes, const std::string& msg);
void deserialize(SerializationType st, const char* bytes, std::string& msg);

Or you can make them template:

template<SerializationType st>
void serialize(char* bytes, const std::string& msg);
template<SerializationType st>
void deserialize(const char* bytes, std::string& msg);

The point is, the user won't have to deal with different types of strings, in their code, they only have to choose the way of serialization/deserialization.

like image 22
Anton Savin Avatar answered Nov 10 '22 11:11

Anton Savin


You have to decouple the computation from the messages. So in your program you work with std::string, and when you need to send/receive strings you pass messages conform with your protocol.

So the only public interfaces of your library/application/whatever should be something like:

int send_message(std::string const &s, int mode, ... destination ... etc);
int receive_message(std::string &s, int mode, ... source ... etc);

(with possible overloads of char *c (null-terminated) for s).

with mode being a flag MODE_CSTRING or MODE_VSTRING.

Internally you send/receive:

  • a vector of null-terminated characters or
  • the size and then the vector of characters.

You don't even need to create classes for cstring and vstring.

like image 1
bolov Avatar answered Nov 10 '22 12:11

bolov