This is a follow-up to this question.
We can implement the visitor pattern for the problem in the previous question, as suggested in this answer:
class Base {
foo(Parent& p) {
p.accept(*this);
}
virtual void visit(Child_A&) = 0;
virtual void visit(Child_B&) = 0;
};
class Parent {
virtual void accept(Base&) = 0;
};
class Child_A: Parent {
void accept(Base& v) {
v.visit(*this);
}
};
class Child_B: Parent {
void accept(Base& v) {
v.visit(*this);
}
};
class Derived_A: Base {
void treat_same(Parent&) {
// ...
}
void visit(Child_A& a) {
treat_same(a);
}
void visit(Child_B& b) {
treat_same(b);
}
};
class Derived_B: Base {
void visit(Child_A&) {
// ...
}
void visit(Child_B&) {
// ...
}
};
But now consider if foo
expects a std::vector<std::shared_ptr<Parent>> const&
as its argument.
Then how can we implement visitor pattern for the problem? Is it possible?
EDIT
foo
passes std::vector<std::shared_ptr<Parent>> const&
to another class, state
. But if all the objects in the vector are of type Child_A
it calls state.method_A
, if all objects in the vector are of type Child_B
it calls state.method_B
and otherwise calls state.method_C
. Only state
works directly with parent
classes.
I hope this clears things a bit.
I'm not quite sure I get what you are after, but I'll give it a go.
As I understand it you want something like the following in your code:
std::vector<std::shared_ptr<Parent>> children;
Base * handler = new Derived_A; // or new Derived_B.
for (child : children) {
// You want this call to correctly distinguish
// between objects of Child_A and Child_B
handler->visit(child);
Unfortunately, C++ does not* allow you to do that. (*keep reading) Since function calls are determined by type and all children are of the type shared_ptr for the purpose of this loop.
Luckily, all is not lost.
You can do two things, both requireing to determining the "real" type of child at runtime.
This requires something along the lines of the following.
class Parent {
public:
enum class SubType {
A,
B,
};
virtual void accept(Base &) = 0;
// Subclasses must implement this to
// allow instances of base to correctly handle the objects.
virtual SubType handle_as() const = 0;
};
The implementation of Base would then include something like the following:
class Base {
void visit( shared_ptr<Parent> p ) {
switch( p->handle_as() ) {
case Parent::SubType::A:
this->accept( *static_ptr_cast<Child_A>(p) );
break;
case Parent::SubType::B:
this->accept( *static_ptr_cast<Child_B>(p) );
break;
}
// In addition to your accept(Child_A &) accept(Child_B &) etc.
};
The other alternative it to use dynamic cast. Which will enable RTTI in your whole executable, this may be what you want in this case, but be aware that it does incur a small performance + executable size cost.
dynamic_cast
will return a nullptr if the cast is illegal, otherwise it will return a valid pointer.
In short:
class Base {
void visit( shared_ptr<Parent> p ) {
if ( dynamic_ptr_cast<Child_A>(p) ) {
this->accept( *dynamic_ptr_cast<Child_A>(p) );
}
else if ( dynamic_ptr_cast<Child_B>(p) ) {
this->accept( *dynamic_ptr_cast<Child_B>(p) );
}
}
// In addition to your accept(Child_A &) accept(Child_B &) etc.
};