C ++ call different class constructors, avoid switching
I am collecting a network packet in my program as an array char*
. The first byte of the array represents the type of the packet received, and for each type, I want to have a separate child class of my common base class Packet
to which I pass the byte array and where it will be interpreted.
I would like to avoid using a switch statement that evaluates the first byte of a packet and calls the appropriate constructor. First, because in OOP, you should avoid switch clauses, and second, because I don't want to add a separate statement to the switch statement every time I add a package class.
I've looked at the Factory Method Pattern, but I'm not sure how it will help me in this situation or if it will solve my problem at all.
Basically, I want to avoid editing my code in 10 different places, just to add one package class.
source to share
If the packet type is always exactly one byte, you can create a simple lookup table like:
struct Packet { virtual ~Packet() {} /* ... */ }; // and abstract
std::map<char, std::unique_ptr<Packet>(*)(char const *, std::size_t)> factory;
std::map<char, std::size_t> packet_size;
Using example:
void handle_input(char const * buf, std::size_t available_size)
{
if (available_size == 0) { return; } // no data
if (packet_size[buf[0]] > available_size) { return } // not enough data
auto p = factory[buf[0]](buf, available_size); // create packet
// process p
// reduce available size by packet_size[buf[0]]
}
Implementation:
struct Type05Packet : Packet
{
static std::unique_ptr<Packet> make(char const * buf, std::size_t len)
{
return std::make_unique<Type05Packet>(buf, len);
}
private:
Type05Packet(char const * buf, std::size_t len) { /* populate */ }
};
And you need to add factory data (for example in main()
):
factory[5] = &Type05Packet::make; // creation function for packet '5'
packet_size[5] = 20; // packet '5' is 20 bytes long
Notes: There are several details that you need to improve.
- The package creation function should first try to parse the data and only call the constructor if the data is valid, otherwise it should return an error condition (eg nullptr).
- Factory and size maps can be filled with some kind of self-registering global constructor. They could also be combined into a single map of pair values for consistency.
- Map lookup should be used
find(buf[0])
to serve unrecognized packet types. - The use case is probably not very realistic. You should be in some kind of situation where you can put data from the buffer as you see fit; The point is that you only do this once, when there is enough data to form the entire packet.
source to share
"I would like to avoid using the switch statement ..."
At some point, you will have to distinguish between this byte, whether you do it in a factory or elsewhere.
The way to avoid switch
is to create a map of functions create_class
, as well as search and call them on the map key (byte value).
This solution will have the advantage that you can easily add additional keys and create_class
functions without changing the underlying factory code.
source to share