First a little explanation of my code to put into context the problem:
I have a class that is responsible for drawing stuff on the screen, I use an overloaded function to draw the different types of drawable entities, the fuctions look like this:
draw(entityType1* name);
draw(entityType2* name);
draw(entityType3* name);
...
All entities classes are derived from a parent "Entity" Class
I wrote a class named "Scene" that has an entity list with all drawable objects in the scene, I'm passing that scene object to the class responsible for drawing stuff on the screen.
The idea is to go through the list and to use function overload to draw the different type of objects on the list, but since the list only contains objects of the type Entity I can't really use the function overload since it only works with the child classes
I'm looking for a code similar to this one
void Painter::draw(Scene* scene) {
std::list<Entity*> drawables = scene->getDrawables();
for (auto it = drawables.begin(); it != drawables.end(); it ) {
draw(*it); //Should apply the correct overload
}
}
This code obviously doesn't work since I dont have any draw() function that takes an entity type. I could always ask for the entity type to do a case to case, but that defeats the purpose of having an overloaded function and breaks the "tell, don't ask" principle.
I'm probably doing something terribly wrong, but I don't really know how to proceed that's why I'm asking the question, I would like to know what's the right approach to solve this problem while respecting the OOP principles, posibilities are wide open, everything is on the table in terms of changing my code.
Thanks in advance
CodePudding user response:
You can use the Visitor pattern to solve this problem. This pattern is delegating the function call to the object itself, so you don't need to use the type of the object to call the correct function.
Here is how you can implement it:
void Painter::draw(Scene* scene) {
std::list<Entity*> drawables = scene->getDrawables();
for (auto it = drawables.begin(); it != drawables.end(); it ) {
(*it)->accept(this); // call virtual function
}
}
// in Entity class
void Entity::accept(Painter* painter) {
painter->draw(this);
}
// in entityType1 class
void entityType1::accept(Painter* painter) {
painter->draw(this);
}
// etc...
More advanced implementation of the visitor pattern can be found here: Implementing the visitor pattern using C Templates
CodePudding user response:
If I'm understanding your question correctly, what you wanted is to have the logic of draw
in the Painter
class, while the original Entity
types remain unchanged. You can achieve this through type erasure:
struct DrawableEntityView
{
template<std::derived_from<Entity> EntityType>
DrawableEntityView(const EntityType& entity)
: p_entity(&entity)
, drawn_by_impl{
[](const Entity* entity, const Painter& painter) {
painter.draw(static_cast<const EntityType*>(entity));
}
}
{}
void drawn_by(const Painter& painter) const
{ drawn_by_impl(p_entity, painter); }
private:
const Entity* p_entity;
void (*drawn_by_impl)(const Entity*, const Painter&);
};
What this essentially does is DrawableEntityView
can be constructed with any derived class of Entity
. When you construct a DrawableEntityView
like:
EntityType1 e1;
DrawableEntityView e1_view{e1};
This will store an Entity*
in e1_view
, and the function pointer drawn_by_impl
will store a lambda that can cast the p_entity
to the type based on what it was constructed with, and call Painter::draw
with it.
Now you can have some code like:
EntityType1 e1;
EntityType2 e2;
EntityType3 e3;
std::vector<DrawableEntityView> vec{e1, e2, e3};
Painter painter;
for(auto& entity: vec)
{
entity.drawn_by(painter);
}
And of course, you can simply add a member function in Painter
like:
void Painter::draw(const DrawableEntityView& entity) const
{ entity.drawn_by(*this); }
Now in the for loop, you can have:
for(auto& entity: vec)
{
painter.draw(entity);
}
Here's a demo: https://godbolt.org/z/fPM4nv3xz
I made this a view purposely, but you can also made DrawableEntity::p_entity
an owning pointer, and remove some of the const. Now you can have the scene store the entities as DrawableEntity
directly.