Reputation: 5586
I have a Message
class that is able to pack its payload to binary and unpack it back. Like:
PayloadA p;
msg->Unpack(&p);
where PayloadA
is a class.
The problem is that I have a bunch of payloads, so I need giant if
or switch
statement:
if (msg->PayloadType() == kPayloadTypeA)
{
PayloadA p;
msg->Unpack(&p); // void Unpack(IPayload *);
// do something with payload ...
}
else if ...
I want to write a helper function that unpacks payloads. But what would be the type of this function? Something like:
PayloadType UnpackPayload(IMessage *msg) { ... }
where PayloadType
is a typedef
of a proper payload class. I know it is impossible but I looking for solutions like this. Any ideas?
Thanks.
Upvotes: 3
Views: 1633
Reputation: 275395
An important question is how the payloads differ, and how they are the same. A system whereby you produce objects of a type determined by the payload, then interact with them via a virtual interface that is common to all types of payload, is reasonable in some cases.
Another option assuming you have a finite and fixed list of types of payload, returning a boost::variant
is relatively easy. Then to process it, call apply_visitor
with a functor that accepts every type in the variant.
If you only want to handle one type of payload differently, a "call and run the lambda if and only if the type matches T" function isn't that hard to write this way.
So you can get syntax like this:
struct State;
struct HandlePayload
{
typedef void return_type;
State* s;
HandlePayload(State* s_):s(s_) {}
void operator()( int const& payload ) const {
// handle int here
}
void operator()( std::shared_ptr<bob> const& payload ) const {
// handle bob ptrs here
}
template<typename T>
void operator()( T const& payload ) const {
// other types, maybe ignore them
}
}
which is cute and all, but you'll note it is quite indirect. However, you'll also note that you can write template code with a generic type T above to handle the payload, and use stuff like traits classes for some situations, or explicit specialization for others.
If you expect the payload to be one particular kind, and only want to do some special work in that case, writing a single-type handler on a boost::variant
is easy.
template<typename T, typename Func>
struct Helper {
typedef bool return_type;
Func f;
Helper(Func f_):f(f_) {}
bool operator()(T const& t) {f(t); return true; }
template<typename U>
bool operator()(U const& u) { return false; }
};
template<typename T, typename Variant, typename Func>
bool ApplyFunc( Variant const& v, Func f )
{
return boost::apply_visitor( Helper<T, Func>(f), v );
}
which will call f on a variant v but only on the type T in the Variant, returning true iff the type is matched.
Using this, you can do stuff like:
boost::variant<int, double> v = 1.0;
boost::variant<int, double> v2 = int(1);
ApplyFunc<double>( v, [&](double d) { std::cout << "Double is " << d << "\n"; } );
ApplyFunc<double>( v2, [&](double d) { std::cout << "code is not run\n"; } );
ApplyFunc<int>( v2, [&](int i) { std::cout << "code is run\n"; } );
or some such variant.
Upvotes: 1
Reputation: 8421
I would split one level higher to avoid the problem entirely:
#include <map>
#include <functional>
...
std::map<int, std::function<void()> _actions;
...
// In some init section
_actions[kPayloadA] = [](IMessage* msg) {
PayloadA p;
msg->Unpack(&p);
// do something with payload ...
};
// repeat for all payloads
...
// decoding function
DecodeMsg(IMessage* msg) {
_actions[id](msg);
}
To further reduce the code size, try to make Unpack
a function template (possible easily only if it's not virtual, if it is you can try to add one level of indirection so that it isn't ;):
class Message {
template <class Payload>
Payload Unpack() { ... }
};
auto p = msg->Unpack<PayloadA>();
// do something with payload ...
Now let's see how we can avoid writing the long list of _actions[kPayloadN]
. This is highly non trivial.
First you need a helper to run code during the static initialization (i.e. before main):
template <class T>
class Registrable
{
struct Registrar
{
Registrar()
{
T::Init();
}
};
static Registrar R;
template <Registrar& r>
struct Force{ };
static Force<R> F; // Required to force all compilers to instantiate R
// it won't be done without this
};
template <class T>
typename Registrable<T>::Registrar Registrable<T>::R;
Now we need to define our actual registration logic:
typedef map<int, function<void()> PayloadActionsT;
inline PayloadActionsT& GetActions() // you may move this to a CPP
{
static PayloadActionsT all;
return all;
}
Then we factor in the parsing code:
template <class Payload>
struct BasePayload : Registrable<BasePayload>
{
static void Init()
{
GetActions()[Payload::Id] = [](IMessage* msg) {
auto p = msg->Unpack<Payload>();
p.Action();
}
}
};
Then we define all the payloads one by one
struct PayloadA : BasePayload<PayloadA>
{
static const int Id = /* something unique */;
void Action()
{ /* what to do with this payload */ }
}
Finally we parse the incoming messages:
void DecodeMessage(IMessage* msg)
{
static const auto& actions = GetActions();
actions[msg->GetPayloadType]();
}
Upvotes: 2
Reputation: 68033
How about a Factory Method that creates a payload according to the type, combined with a payload constructor for each payload type, taking a message as a parameter?
There's no avoiding the switch (or some similar construct), but at least it's straightforward and the construction code is separate from the switch.
Example:
class PayloadA : public Payload
{
public:
PayloadA(const &Message m) {...} // unpacks from m
};
class PayloadB : public Payload
{
public:
PayloadB(const &Message m) {...} // as above
};
Payload * UnpackFromMessage(const Message &m)
{
switch (m.PayloadType) :
case TypeA : return new PayloadA(m);
case TypeB : return new PayloadB(m);
... etc...
}
Upvotes: 2
Reputation: 1207
One good solution is a common base class + all payloads inheriting from that class:
class PayLoadBase {
virtual char get_ch(int i) const=0;
virtual int num_chs() const=0;
};
And then the unpack would look like this:
class Unpacker {
public:
PayLoadBase &Unpack(IMessage *msg) {
switch(msg->PayLoadType()) {
case A: a = *msg; return a;
case B: b = *msg; return b;
...
}
}
private:
PayLoadA a;
PayLoadB b;
PayLoadC c;
};
Upvotes: 0
Reputation: 20620
I seen this solved with unions. The first member of the union is the type of packet contained.
Examples here: What is a union?
Upvotes: 1
Reputation: 69663
You can make the function return a void *
. A void pointer can be cast to any other type.
Upvotes: -1