【问题标题】:c++ particle system inheritancec++粒子系统继承
【发布时间】:2020-05-03 11:07:11
【问题描述】:

我正在创建粒子系统,我希望能够选择将在屏幕上显示的对象类型(例如简单的像素或圆形)。我有一个存储所有参数的类(ParticleSettings),但没有存储点或圆形等的实体。我认为我可以创建纯虚拟类(ParticlesInterface)作为基类,其派生类如ParticlesVertex 或 ParticlesCircles 用于存储这些可绘制对象。是这样的:

class ParticlesInterface
{
protected:
    std::vector<ParticleSettings>   m_particleAttributes;   

public:
    ParticlesInterface(long int amount = 100, sf::Vector2f position = { 0.0,0.0 });
    const std::vector<ParticleSettings>& getParticleAttributes() { return m_particleAttributes; }
...
}

和:

class ParticlesVertex : public ParticlesInterface
{
private:                            
    std::vector<sf::Vertex>         m_particleVertex;
public:
    ParticlesVertex(long int amount = 100, sf::Vector2f position = { 0.0,0.0 });
    std::vector<sf::Vertex>& getParticleVertex() { return m_particleVertex; }
...
}

所以...我知道我无法通过使用多态来访问 getParticleVertex() 方法。我真的很想拥有这种访问权限。我想问是否有更好的解决方案。我在决定如何将所有这些联系在一起时遇到了非常糟糕的时光。我的意思是我也在考虑使用模板类,但我需要它是动态绑定而不是静态的。我认为这种多态性的想法是可以的,但我真的需要在该选项中访问该方法。你能帮我怎么做吗?我想知道这里最好的方法是什么,如果我决定按照上面展示的方式来解决这个问题,我是否有任何好的答案。

【问题讨论】:

  • 你想从哪里访问 getParticleVertex() ?
  • 所以我有另一个类 - ParticlesManage - 我想使用该类中的那个方法。在 ParticleManage 我只是有属性(现在): std::vector<:unique_ptr>> m_explodedParticles;
  • 所以你不能直接调用 m_explodedParticles[some_index]->getParticleVertex() 吗?
  • 但是我应该如何访问派生方法?使用多态时我不能这样做不是吗?
  • getParticleVertex() 仅在您扩展的 ParticlesVertex 类中定义,因此您的基类 ParticlesInterface 没有这样的功能。因此,在这里讨论多态是没有意义的。多态性的唯一用途是当您将 ParticlesInterface 对象的向量存储在某处时,但您将它们视为 ParticlesVertex,因为扩展是一种“是一种”关系(但反之则不然)。

标签: c++ inheritance sfml dynamic-binding


【解决方案1】:

听上去,ParticlesInterface 抽象类不只是有一个虚拟的getParticleVertex,因为这在一般情况下没有意义,只适用于特定类型ParticlesVertex,或者可能是一组相关的类型。

这里推荐的方法是:任何时候您需要根据实际具体类型执行不同操作的代码,让这些“不同的事情”成为接口中的虚函数。

所以从:

void GraphicsDriver::drawUpdate(ParticlesInterface &particles) {
    if (auto* vparticles = dynamic_cast<ParticlesVertex*>(&particles)) {
        for (sf::Vertex v : vparticles->getParticleVertex()) {
            draw_one_vertex(v, getCanvas());
        }
    } else if (auto* cparticles = dynamic_cast<ParticlesCircle*>(&particles)) {
        for (CircleWidget& c : cparticles->getParticleCircles()) {
            draw_one_circle(c, getCanvas());
        }
    }
    // else ... ?
}

CircleWidget是编造的。sf我不熟悉,但这不是重点。)

由于getParticleVertex 对每种ParticleInterface 都没有意义,因此任何从界面中使用它的代码都必须进行某种if 之类的检查,并使用dynamic_cast 来获取实际数据。如果需要更多类型,上面的 drawUpdate 也不可扩展。即使有一个通用的else“应该”处理其他所有事情,一个类型需要一些自定义提示的事实表明,其他一些未来类型或对现有类型的更改可能也需要它自己的自定义行为。相反,将代码对接口执行的操作更改为可以要求接口执行的操作:

class ParticlesInterface {
    // ...
public:
    virtual void drawUpdate(CanvasWidget& canvas) = 0;
    // ...
};

class ParticlesVertex {
    // ...
    void drawUpdate(CanvasWidget& canvas) override;
    // ...
};
class ParticlesCircle {
    // ...
    void drawUpdate(CanvasWidget& canvas) override;
    // ...
};

现在粒子类更加“活跃”——它们积极地做事,而不仅仅是被采取行动。

再举个例子,假设你发现ParticlesCircle,而不是ParticlesVertex,需要在坐标发生变化时更新一些会员数据。您可以将 virtual void coordChangeCB() {} 添加到 ParticlesInterface 并在每个运动模型滴答之后或任何时候调用它。使用接口类中的 {} 空定义,任何像 ParticlesVertex 这样不关心回调的类都不需要覆盖它。

请遵循单一职责原则,尽量保持界面的虚拟功能的意图简单。如果你不能用一两句话写出函数的一般用途或预期行为,那可能太复杂了,也许更容易被认为是更小的步骤。或者,如果您发现多个类中的虚拟覆盖具有相似的模式,那么这些实现中的一些较小的部分可能是有意义的虚拟函数;更大的功能可能会也可能不会保持虚拟,这取决于剩下的功能是否可以被认为是真正通用的接口。

(编程最佳实践是建议,有充分的理由支持,但不是绝对的法律:我不会说“永远不要使用dynamic_cast”。有时出于各种原因,break the rules 可能有意义。)

【讨论】:

  • 哇,谢谢你的回答,现在我想我明白我现在应该做什么了,我会尝试
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
  • 2011-04-14
  • 1970-01-01
  • 1970-01-01
  • 2012-06-28
  • 1970-01-01
相关资源
最近更新 更多