迭代子类列表错误

时间:2013-10-05 21:19:11

标签: c++ inheritance subclassing stdlist

我对C ++比较陌生,来自C#背景我遇到了这个列表迭代的问题:

我有一个循环遍历对象列表的方法,并为每个对象调用一个更新方法,效果很好。该列表的类型为std::list<EngineComponent>,名为engineComponents

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

我还有EngineComponent的子类DrawableEngineComponent

当我尝试进行类似的迭代时会出现问题:

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

这会产生错误“没有从'EngineComponent'到'DrawableEngineComponent'存在合适的用户定义转换”。鉴于这种实现在C#中都很好用,我不确定如何最好地用C ++来解决这个问题。

我可以想到一些可行/应该可行的替代方法,但我想知道C ++中是否有功能以类似于C#的方式执行此操作,而无需手动定义转换。

有关两个类别的定义如下:

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

    virtual void Update(void);
};


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

    virtual void Draw(void);
};

是的,我正在复制XNA框架;)

3 个答案:

答案 0 :(得分:2)

你得到那个错误的实际原因是你定义基于范围的方式,你是通过复制而不是引用来检索对象:

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

EngineComponent是一个超类,因此没有对派生类的隐式强制转换。如果您尝试从DrawableEngineComponent列表中复制EngineComponent,则编译器无法知道源对象是否真的是派生类。

标准容器并不能很好地处理多态对象。更好的解决方案是使用std::shared_ptr来存储指向对象的指针。

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

这会将DrawableEngineComponent包装在共享指针中并将其存储在列表中。可以通过与原始方法类似的方式访问它:

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

但是这次你可以调用一个完全多态的对象。如果对象重载子类中的Update()方法,那么将调用它。如果你需要的话,你也可以使用强制转换来获得指向子类的指针:

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

答案 1 :(得分:2)

std::list<EngineComponent>就是这样;引擎组件对象列表。当您按下列表时,您正在复制正在推送的对象。除非您在子类及其基础之间定义了转换,否则它将失败。同样,尝试从基础转换为子类也会失败。

你可能想要的是一个指向基类对象的指针列表,即:std::list<unique_ptr<EngineComponent>>可能会有所帮助。无论使用何种类型的指针,在调用Draw方法之前,都需要将其向下转换为DrawableEngineComponent:

for (unique_ptr<EngineComponent> & engCompPtr: engineComponents)
{
   DrawableEngineComponent & drawableEngComp = dynamic_cast<DrawableEngineComponent &>(*engCompPtr);
   drawableEngComp.Draw();
}

我不太了解C#,但我假设你在处理对象时,它实际上是通过一些描述的智能指针实现的。

答案 2 :(得分:1)

std::list<EngineComponent>存储了一堆EngineComponents。如果您在列表中添加DrawableEngineComponent,那么您正在将“可绘制”部分切掉:

std::list<EngineComponent> engineComponents;
EngineComponent comp1;
engineComponents.push_back(comp1); // no problem, just copies it into the list
DrawableEngineComponent comp2;
EngineComponent newComp2 = *(EngineComponent*)(&comp2); // the drawable part is now sliced out!
engineComponents.push_back(newComp2); // this adds the sliced version to the list

很可能,您希望存储一个指向EngineComponents的指针列表;但即便如此,建议单独存储可绘制的那些(否则你将需要做一些铸造和检查,以确保它可以铸造到那种类型。