For UDP packages exchanged between client and server I would like to support two kinds of string fields:
cstring
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.
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.
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.
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:
You don't even need to create classes for cstring
and vstring
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With