I write embedded firmware using C++. A common job for firmware is to "handle" different types of "messages" (e.g., in a communication protocol). The "C" way of doing this would be a really big switch statement like so:
void handleMessage(const AbstractMessage& msg)
{
switch (msg.id())
{
case Message1::kId:
//Do something with the specific message by casting
//static_cast<const Message1&>(msg)
break;
case Message2::kId:
//Do something else
break;
}
}
In OOP, a big switch statement like this is often a code smell as well as a maintenance headache. So, I typically leverage the features of C++ to implement this same process using the visitor pattern instead. This way, the compiler is essentially creating my "message lookup table" via virtual methods:
class Visitor
{
public:
virtual void visit(const Message1& msg) {}
virtual void visit(const Message2& msg) {}
};
class MyFantasticClass : public MessageVisitor
{
public:
void handleMessage(const AbstractMessage& msg);
void visit(const Message1& msg) override;
void visit(const Message2& msg) override;
};
void MyFantasticClass ::handleMessage(const AbstractMessage& msg)
{
msg.accept(*this);
}
This is great except for a couple problems.
First, because the "visitor" needs to inherit from an interface, I often find myself having to introduce multiple inheritance into my design. For example, MyFantasticClass
my inherit to introduce a real "is-a" relationship while also needing to inherit from Visitor
to implement a "is implemented in terms of" relationship. This multiple inheritance just adds more complexity and (potentially) overhead (e.g., due to additional "thunk" methods).
Second, I often find my classes visiting a message that originated internally. For example, the MyFantasticClass
class creates a new message via a factory then wants to "visit" this specific message. Since the class inherits from the Visitor
interfance, it now states to the outside world that it "is a" Visitor. But, the intent is not for other classes to use this interface.
My question is, is there another option I can look into?
One of my thoughts was to break the inheritance by introducing a private class which inherits from the visitor interface which is then a member of the main class
class MyFantasticClass : public MessageVisitor
{
public:
void handleMessage(const AbstractMessage& msg);
private:
class Impl : public MessageVisitor
{
public:
void visit(const Message1& msg) override;
void visit(const Message2& msg) override;
};
Impl _impl;
};
void MyFantasticClass ::handleMessage(const AbstractMessage& msg)
{
msg.accept(_impl);
}
But this introduces the added complexity of me somehow needing to perhaps still have access to the members (variables and functions) of the MyFantasticClass
class from within the visitor since it was this class that needed to "handle" the messages in the first place.
Best Answer
Based upon the comments on my question, one possible alternative would be to use std::variant and std::visit as such:
You can see a side-by-side example of using a traditional visitor versus std::variant here: https://godbolt.org/z/hKccz9qTe
Interestingly enough, the variant seems to produce smaller code (which is typically me desire for an embedded system) when compiled identically to the traditionalvisitor (using virtual methods). I confirmed this by compiling both using arm-none-eabi-g++ then running arm-none-eabi-size. This seems a bit odd since, from my understanding, std::visit essentially implements a vtable under the hood in order to do its work.