【问题标题】:Iterating a list of subclasses error迭代子类列表错误
【发布时间】:2013-10-05 21:19:11
【问题描述】:

我对 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 框架;)

【问题讨论】:

  • 该演员尝试指出了一个潜在的切片问题,您最好通过read this question and answers 了解更多信息。
  • 您说这在 C# 中有效,但我不确定这是不是真的 :-) 在 C# 中,如果列表中的组件之一是 not 可绘制的,会发生什么情况。它会崩溃吗?或者跳过列表中的那个项目?请说明您在这方面的期望/期望行为。
  • @AaronMcDaid 你是对的,它没有。写这个的时候有点迷茫。在 C# 中很容易做到,只需检查循环中出现的每个对象(使用一行代码),这是我应该写的。好地方:)

标签: c++ inheritance subclassing stdlist


【解决方案1】:

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

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

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

标准容器并不能很好地处理多态对象。一个更好的解决方案是使用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(我一直在等待你最终取消删除这个东西,所以我可以投票给它=P)。
  • 这正是我所追求的!非常感谢!不过,我没有实现std::shared_ptr,这一切似乎都运行良好。我不确定这样做的利弊是什么,我将不得不阅读它:)
【解决方案2】:

std::list&lt;EngineComponent&gt; 就是这样;引擎组件对象的列表。当您推送到列表时,您正在制作您正在推送的对象的副本。除非您定义了子类与其基类之间的转换,否则它将失败。同样,尝试从基类转换为子类也会失败。

您可能想要的是指向基类对象的指针列表,即:std::list&lt;unique_ptr&lt;EngineComponent&gt;&gt; 可能会解决问题。无论您使用哪种类型的指针,您都需要先将其向下转换为 DrawableEngineComponent,然后才能调用 Draw 方法:

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

我对 C# 了解不多,但我假设当您使用对象时,它实际上是由某种描述的智能指针实现的。

【讨论】:

    【解决方案3】:

    std::list&lt;EngineComponent&gt; 存储了一堆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 的指针列表;但即便如此,建议单独存储可绘制对象(否则您将不得不进行一些转换和检查以确保它可以转换为该类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-19
      • 2020-08-15
      相关资源
      最近更新 更多