Serialization of simple structures without external libraries - c++

I'm trying to serialize simple single level classes like the ones bellow, without external libraries like boost, and without having to implement serializer function for every class. Even though I have so few classes that I could easily implement a serializer for each one, for future reference, I would like to have at hand a simple solution that scales well.
The requirement for each class to be serialized is that its members are only serializable types, and an array of member pointers is defined, so that at serialization the members can be iterated regardless of which class is passed.
The problem is that the compilation fails because of the missing cast where the member pointer is dereferenced, obviously:
esp32.ino: 122:35: error: 'footprint.MessageFootprint<1>::Members[i]'
cannot be used as a member pointer, since it is of type 'void*
I don't know how to store the member pointers in a iterable collection or how to avoid the void* cast.
That's my goal. I want to iterate over class members at serialization having a single generic serialization function.
I don't know what to do.
enum SerializableDataTypes {
SerInt,
SerFloat,
SerString,
SerIntArray
};
template <int N>
struct MessageFootprint {
SerializableDataTypes DataTypes[N];
void* Members[N];
};
template<typename T, typename R>
void* void_cast(R(T::*m))
{
union
{
R(T::*pm);
void* p;
};
pm = m;
return p;
}
class ControlMessage{};
// first structure to be serialized
class Message1 : public ControlMessage {
public:
int prop1;
int prop2;
};
const int Message1MemberCount = 2;
const MessageFootprint<Message1MemberCount> Message1FootPrint = { { SerInt, SerInt }, {void_cast(&Message1::prop1), void_cast(&Message1::prop2)} };
// second structure to be serialized
class Message2 : public ControlMessage {
public:
int prop1;
String prop2;
};
const int Message2MemberCount = 2;
const MessageFootprint<Message2MemberCount> Message2FootPrint = { { SerInt, SerInt }, {void_cast(&Message2::prop1), void_cast(&Message2::prop2)} };
template<int N>
void SerializeMessage(MessageFootprint<N> footprint, ControlMessage message) {
for (int i = 0; i < N; i++) {
if (footprint.DataTypes[i] == SerInt) {
// serialization code here based on data type
// for demonstration purposes it's only written in the serial port
logLine(String(i));
Serial.println(*((int*)(message.*(footprint.Members[i]))));
}
}
}
void main() {
// usage example
Message1 msg = Message1();
msg.prop1 = 1;
msg.prop2 = 2;
SerializeMessage(Message1FootPrint, msg);
}

Don't erase types; that is, don't cast your pointers to void*. If you preserve the types of the pointers through templates, you can choose the deserialization functions directly off their types, and thus you won't even have to specify them. Indeed, you already have a bug where you have marked the second member of Message2 SerInt when it is a String. If you work off the actual types instead of forcing the user to duplicate them, you avoid such errors. Also, the common superclass is completely unnecessary.
template<typename T, typename... Parts>
struct MessageFootprint {
std::tuple<Parts T::*...> parts;
MessageFootprint(Parts T::*... parts) : parts(parts...) { }
};
template<typename T, typename... Parts>
MessageFootprint(Parts T::*...) -> MessageFootprint<T, Parts...>; // deduction guide
// e.g.
struct Message1 {
int prop1;
int prop2;
};
inline MessageFootprint footprint1(&Message1::prop1, &Message1::prop2);
// deduction guide allows type of footprint1 to be inferred from constructor arguments
// it is actually MessageFootprint<Message1, int, int>
// if you are on a C++ standard old enough to not have deduction guides,
// you will have to manually specify them
// this is still better than letting the types be erased, because now the compiler
// will complain if you get it wrong
// e.g. if I replicate your mistake
struct Message2 {
int prop1;
std::string prop2;
};
inline MessageFootprint<Message2, int, int> footprint2(&Message2::prop1, &Message2::prop2);
// This does not go through because ^^^ is wrong
Serialization is probably best handled with overloading. For each Part T::* in a MessageFootprint<T, Part...>, extract a Part& from the T and call out to an overloaded function that decides what to do based on Part:
// I have no idea what serial port communication stuff you're doing
// but this gets the point across
void SerializeAtom(int i) { std::cout << "I" << i; }
void SerializeAtom(std::string const &s) { std::cout << "S" << s.size() << "S" << s; }
template<typename T, typename... Parts>
void SerializeFootprint(MessageFootprint<T, Parts...> footprint, T const &x) {
// calls the provided functor with the things in the tuple
std::apply(
// this lambda is a template with its own Parts2... template parameter pack
// and the argument is really Parts2... parts
// we then do a fold expression over parts
// we need std::apply because there's no simpler way to get the actual
// values out (std::get fails when there are duplicates)
[&x](auto... parts) { (SerializeAtom(x.*parts), ...); },
footprint.parts);
}
// Trying to write ^^^ before C++17 would probably be a nightmare
This system is extensible: to add a new "atomic" type, just overload SerializeAtom. No need to manage an enum or whatnot. Deserialization would mean a family of DeserializeAtom overloads that write into the given reference, and a DeserializeFootprint which would probably look exactly like SerializeFootprint.
Godbolt demonstration

I've developed a serialization system that uses buffering.
Each object inherits from an interface that declares functions for:
1. Returning the size of the object on the stream.
2. Storing the object members to a buffer.
3. Loading the object members from a buffer.
This system is based on the fact that structs and classes can contain padding and that the class/struct is most knowledgeable about its members. For example, a multibyte integer may be Big Endian in the buffer, and the object needs to convert to Little Endian. This system also accommodates different methods for writing variable length text fields.
class Binary_Stream_Interface:
{
public:
// Returns the size, in uint8_t units, that the object occupies in
// a buffer (stream), packed.
virtual size_t size_on_stream() const = 0;
// Loads the class members from a buffer, pointed to by p_buffer.
// The p_buffer pointer will be incremented after loading the object.
virtual void load_from_buffer(uint8_t* & p_buffer) = 0;
// Stores the class members to a buffer, pointed to by p_buffer.
// The p_buffer pointer will be incremented after loading the object.
virtual void store_to_buffer(uint8_t * & p_buffer) const = 0;
};
To serialize (write) an object:
1. Call size_on_stream() to determine the buffer size needed.
2. Allocate the buffer.
3. Call store_to_buffer to store the object into the buffer.
4. Write the buffer to the stream, using std::ostream::write.
5. Delete the buffer.
Reading an object:
1. Call size_on_stream() to determine the buffer size needed.
2. Allocate the buffer.
3. Read the data from the stream into the buffer, using std::istream::read and the size needed.
4. Call the load_from_buffer() method.
5. Delete the buffer.
Implementation is left as an exercise for the OP.
Note: Templates can be used for common POD and std:string to make everything more uniform.
Edit 1: Example
struct Student
: public Binary_Stream_Interface
{
std::string name;
unsigned int id;
size_t size_on_stream() const
{
size_t stream_size = sizeof(id) + sizeof(int) + name.length();
return stream_size;
}
void load_from_buffer(uint8_t* & p_buffer)
{
// Read the string size.
unsigned int length = *((unsigned int *)(p_buffer));
p_buffer += sizeof(length);
// Load the string text from the buffer
name = std::string((char *) p_buffer, length);
p_buffer += length;
id = *((unsigned int *) p_buffer);
p_buffer += sizeof(id);
}
void store_to_buffer(uint8_t * & p_buffer) const
{
unsigned int length = name.length();
*((unsigned int *) p_buffer) = length;
p_buffer += sizeof(unsigned int);
p_char_buffer = (char *) p_buffer;
std::copy(name.begin(), name.end(), p_char_buffer);
p_buffer += length;
*((unsigned int *) p_buffer) = id;
p_buffer += sizeof(unsigned int);
}
};

Related

How to avoid mistakes in functions with same type arguments

How can I avoid mistakes with passing parameters of same type to function?
Let's consider function reading some binary data:
std::vector<uint8_t> read(size_t offset, size_t amount);
It's so easy to mistake offset with amount(I did similar it many times).
I see solution to this:
struct Offset
{
explicit Offset(size_t value) : value{value}{}
size_t value;
};
struct Amount
{
explicit Amount(size_t value) : value{value}{}
size_t value;
};
std::vector<uint8_t> read(Offset offset, Amount amount);
Is there a better solution to avoid mistakes like that?
There are two approaches I can think of.
Tagged Types
This is essentially what you are suggesting in your question, but I would implement it generically.
template <typename Tag, typename T>
struct Tagged
{
explicit Tagged(const T& value) : value{value} { }
T value;
};
template <typename Tag, typename T>
Tagged<Tag, T> tag(const T& value)
{
return Tagged<Tag, T>{value};
}
struct OffsetTag
{ };
struct AmountTag
{ };
using Offset = Tagged<OffsetTag, std::size_t>;
using Amount = Tagged<AmountTag, std::size_t>;
std::vector<uint8_t> read(Offset offset, Amount amount);
This allows you to expand the same concept to other underlying data types.
Named Parameter Idiom
The Named Parameter Idiom is somewhat similar to the Options approach in #PaulBelanger's answer, but it can be used in place and doesn't allow the user to take the the curly-brace shortcut that brings you back to the same problem you had before. However, it will default-initialize all your parameters, so while you are protected from mixing up parameters, it can't force you to provide explicit values for all of them. For your example:
class ReadParams
{
public:
ReadParams() : m_offset{0}, m_amount{128}
{ }
ReadParams& offset(std::size_t offset)
{
m_offset = offset;
return *this;
}
// Could get rid of this getter if you can make the users
// of this class friends.
std::size_t offset() const { return m_offset; }
ReadParams& amount(std::size_t amount)
{
m_amount = amount;
return *this;
}
// Could get rid of this getter if you can make the users
// of this class friends.
std::size_t amount() const { return m_amount; }
private:
std::size_t m_offset;
std::size_t m_amount;
};
std::vector<uint8_t> read(const ReadParams& params);
int main()
{
read(ReadParams{}.offset(42).amount(2048)); // clear parameter names
// read(ReadParams{42, 2048}); // won't compile
read(ReadParams{}.offset(42)); // also possible, amount uses default value
}
You could implement the members of ReadParams as std::optionals and throw a runtime error if an uninitialized member is access; but you can no longer enforce at compile time that the user actually provides all parameters.
Another thing that you can do is to pass parameters in a structure. This also allows you to set sensible defaults for the values. This is especially useful when a constructor takes a large number of arguments. For example:
class FooService
{
public:
// parameters struct.
struct Options
{
ip::address listen_address = ip::address::any();
uint16_t port = 1337;
bool allow_insecure_requests = false;
std::string base_directory = "/var/foo/"
};
//constructor takes the options struct to pass values.
explicit FooService(FooServiceOptions options);
// ...
};
Which is then used like:
FooService::Options o;
o.port = 1338;
//all other values keep their defaults.
auto service = make_unique<FooService>(o);

Constructing array of C++ vectors of different types

Is there a nice way to construct an array of std::vectors of different types? Also Is there a good way of storing those vectors?
For example, let us have some structs Foo, Bar, and Baz. I want to make a container class Cont that hold some combination of Foo, Bar, Baz vectors. The following code will achieve this, but I have some problems with it.
#include <vector>
// arbitrary structs
struct Foo{ int var1; };
struct Bar{ double var1; double var2; };
struct Baz{ char var1; float var2; };
enum Mask{
fooMask = (1<<0),
barMask = (1<<1),
bazMask = (1<<2)
};
class Cont{
void** containers;
public:
Cont(int mask){
// count number of 1s in mask
int count = 0;
int countMask = mask;
while(countMask){
countMask &= countMask-1; // unset rightmost 1
count++;
}
containers = new void*[count];
int index = 0;
if((mask & fooMask) == fooMask)
containers[index++] = new std::vector<Foo>;
if((mask & barMask) == barMask)
containers[index++] = new std::vector<Bar>;
if((mask & bazMask) == bazMask)
containers[index++] = new std::vector<Baz>;
}
};
int main(){
// example construction
Cont c1(fooMask);
Cont c2(barMask|bazMask);
return 0;
}
First, I don't like that I have to store the array of vectors in a void** but I could not figure out a better way.
Second, if I add a new struct called Qux, I would have to modify the Cont constructor. Preferably for maintainability, I would want to construct the array without having to hardcode the struct types into the Cont class.
I've tried to use templates to solve this problem, but couldn't find a solution I was happy with. I'm concerned about making Cont a template as I think it will lead to template bloat for each combination of structs. Also I will have multiple Cont objects but only one of each combination that I need.
You can use type erasure.
struct ContainerBase
{
virtual ~ContainerBase() = 0;
// This is where you can add an interface for common functionality.
// Write the pure virtual functions here and implement/override them in ContainerTyped.
};
inline ContainerBase::~ContainerBase() = default;
template<class T>
struct ContainerTyped : public ContainerBase
{
std::vector<T> values;
};
class Cont
{
std::vector<std::unique_ptr<ContainerBase>> containers;
public:
Cont(int mask) {
// ...
if ((mask & fooMask) > 0)
containers.push_back(std::make_unique<ContainerTyped<Foo>>());
if ((mask & barMask) > 0)
containers.push_back(std::make_unique<ContainerTyped<Bar>>());
}
};
Demo
This is probably more suitable than using e.g. std::any or other preexisting type erasure because 1) you specify that only specific things (your vector containers) can be stored, and 2) you can add a common interface as indicated and even specialize the interface functions in the differently ContainerTyped. But we would need to know more about your use case to detail this benefit.
The problem with void* is always that you need to somehow retain information about what you actually stored because you are circumventing the strong type system. In other words, how would you get the stored thing back into the strong type system? This is exactly the part where the above approach can shine because you could add a virtual print() = 0 in ContainerBase and then create specialized versions for each kind of struct, e.g.
template<>
void ContainerTyped<Foo>::print()
{
for (Foo& foo : values) {
// Print Foo objects as you wish!
}
}
In terms of not having to touch the Cont constructor when adding a Qux struct, you obviously still need to encode the information of "which mask bit belongs to which struct" somehow, but you can extract it from the Cont constructor (and even hide it in a different translation unit):
// Put this implementation wherever, Cont only has to know the signature.
std::unique_ptr<ContainerBase> makeContainer(int mask, unsigned indexBit)
{
if ((mask & fooMask) > 0)
return std::make_unique<ContainerTyped<Foo>>();
// etc.
if ((mask & quxMask) > 0)
return std::make_unique<ContainerTyped<Qux>>();
return nullptr;
}
// ...
Cont::Cont(int mask)
{
for (unsigned indexBit = 0; indexBit < 8; ++indexBit) {
auto container = makeContainer(mask, indexBit);
if (container)
containers.emplace_back(std::move(container));
}
}
You can go about other ways of encoding that enum -> type information, but that's beyond the scope of this question. The key is that you can hide your concrete type behind ContainerBase and use that everywhere you want to refer to "any one of those Containers".

Templates without T parameters in a non-template class

Ok, I read a lot of answers here and there about this problem, but probably since I don't know the proper syntax I can't figure out how to do this.
I have a non-template class which has to implement different static utility functions, mainly for serialization and deserialization. What I currently have is something like this:
class Data_Base : public QObject
{
...
protected:
static QByteArray Serialize(int value);
static int DeserializeInt(QByteArray ser);
static QByteArray Serialize(char *value);
static char *DeserializeCharArr(QByteArray ser);
static QByteArray Serialize(QString value);
static QString DeserializeQString(QByteArray ser);
....
}
Now, I'd prefer to have all the Deserialize* function as a template, since it will be nicer. And as a bonus, have also the Serialize functions as templates, so I will force the user to actually explicitely say which overload to call. Something which can be used this way:
QByteArray ba = Serialize<int>(5);
...
int theValue = Deserialize<int>(ba);
Now, I've tried different approaches, but since all the functions I saw only examples implementing the templates automatically and not one overload at a time I couldn't find out how to make this work.
Of course this is C++, with QT additions.
As stated in the comments, it is called template specialization and looks like this:
class X
{
public:
template<typename T>
static QByteArray Serialize(T const& t);
template<typename T>
static T Deserialize(QByteArray& v);
};
template<>
QByteArray X::Serialize(int const& t)
{
/* ... */
}
template<>
QByteArray X::Serialize(QString const& t)
{
/* ... */
}
template<>
int X::Deserialize(QByteArray& v)
{
/* ... */
}
template<>
QString X::Deserialize(QByteArray& v)
{
/* ... */
}
QByteArray x=X::Serialize(5);
int y=X::Deserialize<int>(x);
When using Serialize you do not need to specify the template parameter because it can be deduced from the argument's
type.
But you cannot deduce by return type, so you need to add the template parameter when using Deserialize.
IMO force your solution with the usage of template specialization could be a bad choice design.
As I've already said in a comment, templates are generally good when your code structure is equal for each data type.
Serialization is a delicate operation (casting, raw memory, etc.) and data structure can define different implicit conversions and produce an UB.
If I had to implement an a "template" behaviour, this would be the first solution (just a scratch!):
struct Foo {
// Some data member variables.
std::string m_nopod;
// Serialize data object. 'It' must to be a output iterator
template<typename It>
void serialize(It out_iterator) {
constexpr size_t BYTES_FOR_SIZE = sizeof(decltype(m_nopod.size()));
constexpr size_t BYTES_FOR_CHAR = sizeof(decltype(m_nopod)::value_type);
// size definitions.
const auto len_str = m_nopod.size();
const auto len_data = BYTES_FOR_CHAR * len_str;
// Temporary memory buffers.
uint8_t memory_size[BYTES_FOR_SIZE];
auto memory_data = std::make_unique<uint8_t[]>(len_data);
// Raw bytes copy.
std::memcpy(memory_size, &len_str, BYTES_FOR_SIZE);
std::memcpy(memory_data.get(), m_nopod.data(), len_data);
// write with the iterator.
for (size_t i = 0; i < BYTES_FOR_SIZE; ++i) {
*out_iterator = memory_size[i];
}
for (size_t i = 0; i < len_data; ++i) {
*out_iterator = memory_data[i];
}
}
};
Where the out_iterator must to be a output_iterator, and ::value_type must to be a implicit convertible type to unsigned char.
The function can be invoked with different data structures (containers):
int main(int argc, char *argv[]) {
std::vector<char> memory_buffer_char;
std::vector<int> memory_buffer_int;
std::string memory_buffer_str;
Foo foo{"a_string"};
foo.serialize(std::back_inserter(memory_buffer_char));
foo.serialize(std::back_inserter(memory_buffer_int));
foo.serialize(std::back_inserter(memory_buffer_str));
return 0;
}
As I've already said, however, I'll never adopt that solution. Rather I'm going to use a simple overloading of function for those various types.
In order avoid writing same thing more than once, I'll define a unique helper function (a private method) which contains the logic of the class.
For example the helper function could create an ordinary buffer of memory in which to serialize the class (array of char) and then overloaded functions should only adapt that array in the proper input data structure.
In that way when the class logic (e.g., data members) changes, you should modify only the helper function.

Solution to void* and const void* for function parameter -> struct member

So I am using this telegram/message dispatcher system for my A.I. which comes from Matt Buckland's "Programming Game A.I. by Example" book.
I have this method for the MessageDispatcher class:
void DispatchMsg(double delay, int sender, int receiver, int msg, void *additionalInfo = nullptr);
Which then uses a Telegram struct:
struct Telegram
{
// Messages can be either dispatched immediately or delayed for
// a specified amount of time. If a delay is necessary, this
// field is stamped with the time the message should be dispatched.
double DispatchTime;
// Who is sending this message
int Sender;
// Who should the component give this message to
// may be set to -1 if not required
int Receiver;
// Message itself, should be one of the several enumerated types
int Msg;
// Additional information that might want to be passed along
void *ExtraInfo;
Telegram():DispatchTime(-1),
Sender(-1),
Receiver(-1),
Msg(-1)
{
// Empty
}
Telegram(double time,
int sender,
int receiver,
int msg,
void *info = nullptr):DispatchTime(time),
Sender(sender),
Receiver(receiver),
Msg(msg),
ExtraInfo(info)
{
// Empty
}
};
With a cast like:
template <class T>
inline T DereferenceToType(void *p)
{
return *(T*)(p);
}
The trouble lies here:
void Player::playerFeed() {
if (!Target)
return;
Courier->DispatchMsg(SEND_MSG_IMMEDIATELY, PLAYER_ID, TargetedActor, MessageType::PLAYER_FED, &ActorNode->getPosition());
}
Where ActorNode->getPosition() is from Ogre3d Ogre::SceneNode:
virtual const Vector3 & getPosition (void) const
Gets the position of the node relative to it's parent.
I then get it back doing:
Ogre::Vector3 attackerPos = DereferenceToType<Ogre::Vector3>(msg.ExtraInfo);
I would prefer to use a const Ogre::Vector3 here and that can be done writing a const dereference helper function.
Anyway, the problem is:
xxx|90|warning: invalid conversion from 'const void*' to 'void*' [-fpermissive]|
I understand the warning; but I'm not sure how to correct the problem.
I tried fixing it by writing a second method for DispatchMsg:
void DispatchMsg(double delay, int sender, int receiver, int msg, void *additionalInfo = nullptr);
// For const void*
void DispatchMsg(double delay, int sender, int receiver, int msg, const void *additionalInfo);
But that moved the warning into the function at the creation of the Telegram.
So I tried some things like making a second parameter in my Telegram structure called const void *ConstExtraInfo, the trouble is this seems to make the Telegram structure messy imo.
Basically my question is: is there a clean way implementation for this or if it must be done by having extra members within the Telegram to find out which type of information is stored?
Can it be done with a template for void* or const void* like: Telegram or would this complicate the receiving end of the telegram?
Please let me know if I need to post more information on this.
Thanks.
Your first problem is that the additionalInfo-pointer should be const-qualified.
Then, your template should also use const.
Finally, it should return a reference and not copy the data:
template <class T> inline const T& DereferenceToType(const void *p)
{
return *(const T*)p;
}
Anyway, why hide the cast? Instead, do it like this in the receipient:
const auto& extra = *(T*)p;
You could use a const_cast or a plain C style cast to throw away const. This would be the quickest and dirtiest way.
Now, what you are really trying to achieve here is to covert to an intermediate data type that can be cast back and forth from an abstract type to a concrete type. One possible way of doing this is to use what some refer to as a Variant, which is a class/struct that holds some opaque data and a tag that identifies this data. Something similar to:
enum DataTag {
DATA_INT,
DATA_STRING,
DATA_VEC3,
// etcetera
};
struct Variant {
virtual DataTag GetTypeTag() const = 0;
virtual int AsInt() const = 0;
virtual string AsString() const = 0;
virtual Vec3 AsVector() const = 0;
// same goes for assigning a value. I.e: FromInt()/FromString()
};
template<class T>
struct VariantImpl : public Variant {
// add constructors as needed
VariantImpl(const T & data, DataTag tag)
{
this->data = data;
this->tag = tag;
}
// implement the proper conversions
int AsInt() const { }
string AsString() const { }
Vec3 AsVector() const { }
DataTag GetTypeTag() const { return tag; }
T data;
DataTag tag;
};
Then you could have a pointer to a Variant in the Telegram struct and set the ExtraInfo with:
telegram->ExtraInfo = new VariantImpl<int>(42);
And then access it at any time with AsInt(), as long as you check the type tag first to ensure the conversion is allowed.
From this, you can add a lot to it to fit your needs. Hope it helps.

Runtime value to type mapping

I've got a list of types which can be send over the network, take this example:
enum types {
E_T1,
E_T2,
E_T3,
E_T4
};
Now I have a list of classes which correspond to each of the types, let's say each is declared as class E_T1 {...}, class E_T2 {...}, etc.
They are not derived from a common base class and it's not possible to do so. Each of the classes has a verification method I need to invoke with the data send over the network. The client sends the data D and a id correspointing to the message type. I need to get hold of the object corresponding to the type. I can use C++0x features if needed.
What I've tried so far is using specialized templates for the types, holding a typedef for the object related to it. This was obviously a stupid idea as templates parameters need to be compile time constant so doing something along getType<data.id()>::type is not possible.
Then I tried using Boost.Variant to get a common returnable type like this (used mpl vector to iterate over the registered types at runntime for debbuging):
template <typename C>
struct getType() {
typedef C type;
}
typedef boost::mpl::vector<
getType<E_T1>,
getType<E_T2>,
getType<E_TX>...
> _types;
typedef boost::make_variant_over<_types>::type _type;
//use a map to store each type <-> id
boost::unorderd_map<types, _type> m;
m[E_T1] = getType<E_T1>();
m[data.id()]::type x; //<- access type, can now call x.validate(data)
The problem with this is that it's limited to 20 entries per variant per default. This can be overwritten but from what I understood the overhead per type should be considered and we are talking about a few thousand types here.
Also tried boost.any but it doesn't hold any type information so that's out of the question again. Has anyone any good ideas how this can be solved elegantly?
Looking for something where I don't have to write a 1k switch statement anytime I handle a type.
All types are nown at compile type, same goes for their corresponding IDs.
Id -> Type resolving needs to happen at runtime though.
Thanks in advance,
Robin.
External Polymorphism (*)
It's a widely known idiom, however it's widely used: I first encountered it in the shared_ptr implementation and it's been quite useful in my toolbox.
The idea is to actually create a base class for all those types. But not having them derive from it directly.
class Holder {
public:
virtual ~Holder() {}
virtual void verify(unsigned char const* bytes, size_t size) const = 0;
}; // class Holder
template <typename T>
class HolderT: public Holder {
public:
HolderT(): _t() {}
virtual void verify(unsigned char const* bytes, size_t size) const {
_t.verify();
}
private:
T _t;
}; // class HolderT
template <typename T>
std::unique_ptr<Holder> make_holder() {
return std::unique_ptr<Holder>(new HolderT<T>());
}
So, it's the classic strategy of adding a new level of indirection.
Now, you obviously do need a switch to move from value to class. Or perhaps... a map ?
using maker = std::unique_ptr<Holder> (&)();
using maker_map = std::unordered_map<types, maker>;
std::unique_ptr<Holder> select(types const E) {
static maker_map mm;
if (mm.empty()) {
mm.insert(std::make_pair(E_T1, make_holder<EC_T1>));
// ...
}
maker_map::const_iterator it = mm.find(E);
if (it == mm.end()) { return std::unique_ptr<Holder>(); }
return (*it->second)();
}
And now you can handle them polymorphically:
void verify(types const E, unsigned char const* bytes, size_t size) {
std::unique_ptr<Holder> holder = select(E);
if (not holder) { std::cerr << "Unknown type " << (int)E << "\n"; return; }
holder->verify(bytes, size);
}
Of course, you're welcome to make the strategy vary according to your needs. For example moving the map out of select so that you can register your types dynamically (like for plugins).
(*) At least that's the name I have for it, I would quite happy to find out it's already been named.
I'll assume you have a generic way of handling a message, such as for example an overloaded function:
void handle_message(const E_T1& msg);
void handle_message(const E_T2& msg);
//...
Now, you do not really need to get the object's type. All you need is a way to handle a message of that type, given the undecoded message.
So, I recommend you populate a map of factory functions:
std::unordered_map<types, std::function<void (unsigned char const* bytes, size_t size)> handlers;
handlers[E_E1] = [](unsigned char const* bytes, size_t size) { handle_message(E_T1(bytes, size)); };
// ...
Then, once you've decoded the type, you can use handlers[type](bytes, size) to decode and handle a message.
Try variadic templates and your already defined getType class:
enum types { T1_ID, T2_ID, .... };
class T1; class T2; class T3; ....
template <types t> struct getType;
template <> struct getType<T1_ID> { typedef T1 type; };
template <> struct getType<T2_ID> { typedef T2 type; };
...
And the operation verify:
template <types...>
struct type_operation;
template <types t1, types... rest>
struct type_operation<t1, rest...>
{
void verify(types t)
{
if (t == t1)
{
typename getType<t1>::type a;
a.verify(); // read from network and verify the rest of data....
}
else type_operation<rest...>::verify(t, data);
}
};
template <>
struct type_operation<>
{
void verify(types t)
{
ostringstream log; log << "not suppoted: " << t;
throw std::runtime_error(log.str()); //
}
};
Usage:
typedef type_operation<T1_ID, T2_ID, T3_ID, ,,.., TN_ID> type_mapping;
types id;
readFromNetwork(id);
type_mapping::verify(id);