Search code examples
c++inheritancesubclassingstdlist

Iterating a list of subclasses error


I'm relatively new to C++, and coming from a C# background I'm having trouble with this list iteration:

I have one method which loops through a list of objects and calls an update method for each one, which works great. The list is of type std::list<EngineComponent> and is called engineComponents.

void Game::Update()
{
    for (EngineComponent component: this->engineComponents)
    {
        component.Update();
    }
}

I also have a subclass of EngineComponent called DrawableEngineComponent.

The issue appears when I try to do a similar iteration:

void Game::Draw()
{
    for (DrawableEngineComponent component: this->engineComponents)
    {
        component.Draw();
    }
}

This produces the error "No suitable user-defined conversion from 'EngineComponent' to 'DrawableEngineComponent' exists". Given that this implementation was all fine and dandy in C#, I'm not really sure how best to address this issue in C++.

I can think of a few alternative ways that would/should work, but I'm wondering if there's functionality in C++ to do this in a way similar to C# without having to manually define a conversion.

The definitions for the two classes concerned are as follows:

class EngineComponent
{
public:
    EngineComponent(void);
    ~EngineComponent(void);

    virtual void Update(void);
};


class DrawableEngineComponent : public EngineComponent
{
public:
    DrawableEngineComponent(void);
    ~DrawableEngineComponent(void);

    virtual void Draw(void);
};

And yes, I am copying the XNA framework a bit ;)


Solution

  • The actual reason you get that error is that the way you have defined the range-based for, you are retrieving objects by copy rather than reference:

    for (EngineComponent component: this->engineComponents)
    {
         // component is a copy of the object in the list
    }
    

    EngineComponent is a super-class and so there's no implicit cast to a derived class. If you try to copy a DrawableEngineComponent out of a list of EngineComponent the compiler has no way of knowing if the source object is really a derived class.

    Standard containers don't really handle polymorphic objects very well. A much better solution is to use std::shared_ptr to store pointers to the objects.

    std::list<std::shared_ptr<EngineComponent>> myList;
    myList.push_back(std::make_shared<DrawableEngineComponent>());
    

    This will wrap a DrawableEngineComponent in a shared pointer and store it in the list. It can be accessed in a similar way to your original method:

    for (auto& component: engineComponents)
    {
        component->Update();
    }
    

    But this time you have a fully polymorphic object you can call. If the object overloads the Update() method in a sub-class then it's this that will be called. You can also use casting to get a pointer to the sub-class if that's what you need:

    for (auto& component: engineComponents)
    {
        auto pDrawComponent = dynamic_cast<DrawableEngineComponent*>(component.get());
        if (pDrawComponent)
        {
            // it's drawable
        }
    }