dpalma
dpalma

Reputation: 510

C++ passing a list of a class with elements of a subclass

I am sort of a noob in C++ and I am trying to make a simplegame. This is my problem:

I created class called sprite:

class Sprite
{
private:
    Point2D sp_pos;
    Point2D sp_vel;
    SDL_Surface* sp_img;
    Point2D sp_center;
    Point2D sp_size;
    double sp_radius;
    bool sp_animated;
    int sp_frames;
    int sp_cur_frame;

public:
    Sprite() {}
    Sprite(Point2D pos, Point2D vel, SDL_Surface *img, ImageInfo info, bool animated = false, int frames = 0);
    virtual void draw(SDL_Surface* screen);
    virtual void update();
    void setInfo (ImageInfo info);
    void setPos( Point2D pos ) { sp_pos = pos; }
    void setVel( Point2D vel ) { sp_vel = vel; }
    void setImg (SDL_Surface* img) { sp_img = img; }
    void setNextFrame() { sp_cur_frame++; }
    void setFrame( int frame ) { sp_cur_frame = frame; }
    void setAnimated(bool animated) { sp_animated = animated; }
    void changeVelX (int c) { sp_vel.setX(c);}
    void changeVelY (int c) { sp_vel.setY(c);}
    void changePosX (int c) { sp_pos.setX(c);}
    void changePosY (int c) { sp_pos.setY(c);}
    SDL_Surface* getImg() { return sp_img; }
    Point2D getPos() { return sp_pos; }
    Point2D getVel() { return sp_vel; }
    Point2D getCenter() { return sp_center; }
    Point2D getSize() { return sp_size; }
    double getRadius() { return sp_radius; }
    int getCurFrame() { return sp_cur_frame; }
    int getFrames() { return sp_frames; }
    bool collide(Sprite &another_sprite);
};

Which has a method called "collide", this method detects a collision between two sprites, and works as follows:

bool Sprite::collide(Sprite &another_sprite)
{
    double d = getPos().dist(another_sprite.getPos());

    if ( d < ( getRadius() + another_sprite.getRadius() ) )
        return true;
    else
        return false;
}

The method works fine. My problem arises with the following, I have implemented different classes that are subclasses of "Sprite" and will represent enemies in my game, so, for instance I would have objects: Class enemy1 : public Sprite, Class enemy2 : public Sprite, etc. They are different because they have different behaviors. I implemented other two helper functions called group_collide and group_group_collide, that work as follows:

bool group_collide(std::list<Sprite> &group, Sprite other_object)
{
   bool collision = false;

   for (std::list<Sprite>::iterator sprite = group.begin(), end = group.end(); sprite != end; ++sprite)
   {
       if (sprite->collide(other_object))
       {
            Sprite exp = Sprite(sprite->getPos(), Point2D(0, 0), exp_image, exp_info, true, 7);
            exp_group.push_back(exp);
            if( Mix_PlayChannel( -1, explosion, 0 ) == -1 )
            {
                //abort();
            }
            sprite = group.erase(sprite);
            collision = true;
       }
   }
   return collision;
}

int group_group_collide(std::list<Sprite> &group, std::list<Sprite> &other_group)
{
   int scored = 0;

   for (std::list<Sprite>::iterator it1 = group.begin(), end1 = group.end(); it1 != end1; ++it1)
   {
        if (group_collide(other_group, *it1))
        {
            it1 = group.erase(it1);
            scored += 10;
        }

   }
   return scored;
}

So, in fact, group collide will detect collisons between a sprite and a list of sprites, and group_group_collide will detect collisions between group of sprites (two different lists). The problem that arises is: There will be at least 4 types of enemies, and all of them are subclasses of my Sprite class, but I get compilation errors when I create a list of sprites and add elements that are subclasses of sprites. My solution was writing a method group_collide and group_group collide for all types of enemies, but this is quite inelegant. Is there a better way to approach this problem?

EDIT:

Thanks for your suggestions. I defined the list as a pointers list as you have suggested:

std::list<Sprite*> enemy_group;

And for instance, I am adding elements of class "Kamikaze" which is a subclass of sprite, in this way (the method update is different in this class):

enemy_group.push_back(new Kamikaze(enemy_pos, enemy_vel, 0, enemy_image, enemy_info));

However, when iterating over the list:

for (list<Sprite*>::iterator it = enemy_group.begin(), end = enemy_group.end(); it != end; ++it) {
                (*it)->draw(screen);
                (*it)->update();
                if ((*it)->getPos().getY() > SCREEN_HEIGHT + 30)
                {
                    delete *it;
                    it = enemy_group.erase(it);
                }
            }

The method update is called from the Sprite class, not Kamikaze class, therefore I also have object slicing problem with this approach, perhaps there is something wrong with what I have done?

Upvotes: 0

Views: 1738

Answers (2)

cruelcore1
cruelcore1

Reputation: 578

  1. As given in the answer by Laserbreath, you should replace std::list<Sprite> with std::list<Sprite*>. Currently, any passed subclass will be reduced to Sprite. Using a list of pointers you can avoid that.

  2. You should use virtual functions to get the desired result.

In base class:

//This way you'll have a default collision method for a Sprite...
//...but you'll be able to re-define it in subclasses.
//I don't do THIS ONE often so I'm not sure if you'll have to perform additional steps

virtual bool Sprite::collide(Sprite &another_sprite)
{
    ...
}

//And this way you are making collied a pure virtual function and Sprite an abstract class...
//...meaning you can't instantiate it, but you can use it with a pointer or reference.
//Every subclass of Sprite must have a body of function collide defined

//you do this only in header of class Sprite
virtual bool virtual bool Sprite::collide(Sprite &another_sprite) = 0;

In sub-class

//if you have made collide pure virtual function, you can do this
//not sure about the first case though
bool SubSprite::collide(Sprite &another_sprite)
{
    ...
}

So, when collide method is called from a Sprite pointer or reference, the call will be re-directed to the designated function of a subclass.

Same goes for group_collide.

ADDITIONAL EXPLANATION:

//lets assumme you have 2 sprite types, Kamikaze and NotKamikaze
Sprite *sprite1 = new Kamikaze();
Sprite *sprite2 = new NotKamikaze();

//and lets assume collide uses pointer instead, just for this example, so it's shorter
virtual bool Sprite::collide(Sprite *another_sprite) = 0;

bool Kamikaze::collide(Sprite *another_sprite);
bool NotKamikaze::collide(Sprite *another_sprite);

//when you call collide from Sprite*, it **automatically** picks the sub-class collude method

//sprite1 is of type Sprite*, but allocated memory is of Kamikaze
//so the only function called is Kamikaze::collide
sprite1->collide(sprite2); 

//sprite2 is of type Sprite*, but allocated memory is of NotKamikaze
//so the only function called is NotKamikaze::collide
sprite2->collide(sprite2); 

So whenever a pure virtual function collide is called from pointer or reference, the program automatically selects the right one. If you want it to be different for every sprite type, then this is what you need.

This is where "object slicing" becomes the issue. Sprite can exist as a pointer or reference, but only to a non-abstract sub-class. So Sprite *Sprite sprite1 = new Sprite(); won't work, but Sprite *Sprite sprite1 = new SpriteSubClass(); works.

If you make Sprite::collide not purely virtual, Sprite would no longer be an abstract class, and you could make the method run from Sprite class itself, and you could do Sprite *Sprite sprite1 = new Sprite();. But I highly doubt you'll need that as you're only looking at subclasses.

Whenever a method name is shared, but its behavior differs by subclass, it must be virtual.

Upvotes: 0

Bryn McKerracher
Bryn McKerracher

Reputation: 683

I get compilation errors when I create a list of sprites and add elements that are subclasses of sprites.

All derived classes will be 'sliced' in order to fit into an object container like std::list. A std::list<Sprite*> //(Preferably a smart pointer) will avoid this problem, though the actual objects would have to be stored elsewhere.

Upvotes: 3

Related Questions