0

i'm creating particle system and i want to have possibility to choose what kind of object will be showing on the screen (like simply pixels, or circle shapes). I have one class in which all parameters are stored (ParticleSettings), but without those entities that stores points, or circle shapes, etc. I thought that i may create pure virtual class (ParticlesInterface) as a base class, and its derived classes like ParticlesVertex, or ParticlesCircles for storing those drawable objects. It is something like that:

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; }
...
}

and :

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; }
...
}

So... I know that i do not have access to getParticleVertex() method by using polimorphism. And I really want to have that access. I want to ask if there is any better solution for that. I have really bad times with decide how to connect all that together. I mean i was thinking also about using template classes but i need it to be dynamic binding not static. I thought that this idea of polimorphism will be okay, but i'm really need to have access to that method in that option. Can you please help me how it should be done? I want to know what is the best approach here, and also if there is any good answer to that problem i have if i decide to make that this way that i show you above.

Tojmak
  • 155
  • 1
  • 7
  • From where do you want to access getParticleVertex() ? – Hack06 May 03 '20 at 11:29
  • So i have another class - ParticlesManage - and i want to use that method from that class. in ParticleManage i simply have attribute (for now) : std::vector> m_explodedParticles; – Tojmak May 03 '20 at 11:33
  • so can't you just call m_explodedParticles[some_index]->getParticleVertex() ? – Hack06 May 03 '20 at 11:38
  • but how should i get access to derived method? I cannot do that while using polimorphism isnt it? – Tojmak May 03 '20 at 11:41
  • getParticleVertex() is only defined in your extended ParticlesVertex class, so your base class ParticlesInterface has no such function. Therefore, there's no point in talking about polymorphism here. The only use of polymorphism is when you store a vector of ParticlesInterface objects somewhere, but you treat them as ParticlesVertex, because extension is a "is a" relationship (but not vice versa). – Hack06 May 03 '20 at 11:46
  • yeah, so why not use polymorphism? I store vector of ParticlesInterface in ParticleManage class. I need it that way because there will be some other classes like ParticlesVertex class, but not with single pixels but whole objects like circles. – Tojmak May 03 '20 at 11:49
  • Sorry, I don't get clearly your question. What are you trying to do exactly and why is it failing? – Hack06 May 03 '20 at 11:57
  • Okay so one more time to be sure that we know what's going on ;d. And in advance for for trying to help me. Okay, so i have ParticleManage class in which i have vector of pure virtual class - ParticlesInterface. I think that i need that to be pure virtual because in my opinion i should be using polymorphism here. ParticlesInterface will be base class for ParticlesVertex which you already know, but also propably something like ParticlesCircles, ParticlesQuads, and so on. Those classes have methods that are needed for drawing them on window (like Vertex, or CircleShape - those are also classes) – Tojmak May 03 '20 at 12:08
  • Is it more clear now? I tried my best. So one more time. I need access to those objects like: Vertex, CircleShape and so on, and I stored them in ParticlesVertex, ParticlesCircles, etc classes. – Tojmak May 03 '20 at 12:10

1 Answers1

0

From the sounds of it, the ParticlesInterface abstract class doesn't just have a virtual getParticleVertex because that doesn't make sense in general, only for the specific type ParticlesVertex, or maybe a group of related types.

The recommended approach here is: Any time you need code that does different things depending on the actual concrete type, make those "different things" a virtual function in the interface.

So starting from:

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 is made up. I'm not familiar with sf, but that's not the point here.)

Since getParticleVertex doesn't make sense for every kind of ParticleInterface, any code that would use it from the interface will necessarily have some sort of if-like check, and a dynamic_cast to get the actual data. The drawUpdate above also isn't extensible if more types are ever needed. Even if there's a generic else which "should" handle everything else, the fact one type needed something custom hints that some other future type or a change to an existing type might want its own custom behavior at that point too. Instead, change from a thing code does with the interface to a thing the interface can be asked to do:

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

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

Now the particles classes are more "alive" - they actively do things, rather than just being acted on.

For another example, say you find ParticlesCircle, but not ParticlesVertex, needs to make some member data updates whenever the coordinates are changed. You could add a virtual void coordChangeCB() {} to ParticlesInterface and call it after each motion model tick or whenever. With the {} empty definition in the interface class, any class like ParticlesVertex that doesn't care about that callback doesn't need to override it.

Do try to keep the interface's virtual functions simple in intent, following the Single Responsibility Principle. If you can't write in a sentence or two what the purpose or expected behavior of the function is in general, it might be too complicated, and maybe it could more easily be thought of in smaller steps. Or if you find the virtual overrides in multiple classes have similar patterns, maybe some smaller pieces within those implementations could be meaningful virtual functions; and the larger function might or might not stay virtual, depending on whether what remains can be considered really universal for the interface.

(Programming best practices are advice, backed by good reasons, but not absolute laws: I'm not going to say "NEVER use dynamic_cast". Sometimes for various reasons it can make sense to break the rules.)

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Wow, thanks for that answer, now i'm thinking that i understand what i should do now, i will try – Tojmak May 03 '20 at 13:04