I am using the Visitor design pattern in my application for message processing. For example:
class AbstractMessageVisitor;
class AbstractMessage {
public:
virtual void accept(AbstractMessageVisitor& visitor) const = 0;
};
class A : public AbstractMessage {
public:
void accept(AbstractMessageVisitor& visitor) const override { visitor.visit(*this); }
};
class B : public AbstractMessage {
//...
};
class AbstractMessageVisitor {
friend class A;
friend class B;
protected:
virtual void visit(const A&) {};
virtual void visit(const B&) {};
};
In my application, instances of the messages are being created as std::shared_ptr. If I "receive" one of these messages created by the factory, I would "visit" it by doing the following:
class MessageHandler : public AbstractMessageVisitor {
public:
void handleMessage(std::shared_ptr<const AbstractMessage> msg) {
msg->accept(*this);
}
protected:
void visit(const A& msg);
void visit(const B& msg);
};
In this case, I realized that my visit
methods may need to "save the message for later." Since I know that the message that is being visited is managed by a std::shared_ptr, I figured I could just copy the shared_ptr to use later.
However, this is where I encountered my problem; within the visit
methods there is no shared_ptr.
Here are some of the solutions I found to the problem:
Option 1
Add a member variable to MessageHandler class that temporarily stores the shared_ptr while the message is being visited. If the visit method needs the pointer, it makes a copy of this member variable.
class MessageHandler : public AbstractMessageVisitor {
public:
void handleMessage(std::shared_ptr<const AbstractMessage> msg) {
_ptr = msg;
msg->accept(*this);
_ptr.reset();
}
protected:
void visit(const A& msg) {
auto thePointer = std::static_pointer_cast<const A>(_ptr);
}
void visit(const B& msg);
private:
std::shared_ptr<AbstractMessage> _ptr;
};
Obviously, this has many problems with it. You have to cast the temporary shared pointer into the appropriate type. This kind of defeats the purpose of the visitor pattern. You have this "shared state" that must be kept as a member variable.
Option 2
Inherit from std::enable_shared_from_this.
class A : public AbstractMessage, std::enable_shared_from_this<A> {
public:
inline auto ptr() const { return shared_from_this(); }
//...
};
However, this only works if you can be guaranteed that the class is owned by a shared_ptr. On GCC, if it is not, it looks like an exception will be thrown or, if exceptions are disabled, the program will immediately exit.
Looking at the implementation of enable_shared_from_this, I wonder why shared_from_this could not just return a nullptr if the object is NOT owned by a shared_ptr, but alas...
Option 3
Just make the object "cloneable".
class A : public AbstractMessage {
public:
std::shared_ptr cloneShared() { return std::make_shared<A>(*this); }
//...
};
This isn't really a solution, as it is not taking advantage of the fact that a shared_ptr to the object already exists.
So, my questions are:
- Is there a way to accomplish what I am trying to do?
- Perhaps this problem is coming from a flaw in my design; is there something I should change?