C++ 传递一个 class 的列表和一个 subclass 的元素

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

我是 C++ 的新手,我正在尝试制作一个简单的游戏。这是我的问题:

我创建了 class 名为 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);
};

其中有一个方法叫做"collide",这个方法检测两个精灵之间的碰撞,其工作原理如下:

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

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

该方法工作正常。我的问题出现在以下方面,我已经实现了不同的 classes,它们是 "Sprite" 的子classes,并且将在我的游戏中代表敌人,所以,例如我会有对象:Class enemy1 : public Sprite, Class enemy2 : public Sprite 等。它们是不同的,因为它们有不同的行为。我实现了另外两个名为 group_collide 和 group_group_collide 的辅助函数,其工作方式如下:

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

所以,实际上,group collide 将检测精灵和精灵列表之间的碰撞,而 group_group_collide 将检测精灵组(两个不同的列表)之间的碰撞。出现的问题是:至少会有 4 种类型的敌人,它们都是我的 Sprite class 的子class,但是当我创建一个 sprite 列表时出现编译错误并且添加子 class 精灵的元素。我的解决方案是为所有类型的敌人编写一个 group_collide 和 group_group 碰撞的方法,但这很不优雅。有没有更好的方法来解决这个问题?

编辑:

感谢您的建议。我按照您的建议将列表定义为指针列表:

std::list<Sprite*> enemy_group;

例如,我添加了 class "Kamikaze" 的元素,它是 sprite 的子 class,以这种方式(方法更新在这个 class):

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

但是,在遍历列表时:

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

方法更新是从 Sprite class 调用的,而不是 Kamikaze class,因此我也遇到了对象切片问题,也许我所做的有问题?

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

所有派生的 类 都将是 'sliced',以便适合像 std::list 这样的对象容器。 std::list<Sprite*> //(Preferably a smart pointer) 将避免此问题,但实际对象必须存储在其他地方。

  1. 正如 Laserbreath 在回答中给出的那样,您应该将 std::list<Sprite> 替换为 std::list<Sprite*>。目前,任何通过的 subclass 都将缩减为 Sprite。使用指针列表可以避免这种情况。

  2. 您应该使用虚函数来获得想要的结果。

在基地 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;

中分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)
{
    ...
}

因此,当从 Sprite pointerreference 调用 collide 方法时,调用将是 重定向到子class.

的指定函数

group_collide也是如此。

补充说明:

//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); 

因此,每当从指针或引用调用纯虚函数 collide 时,程序 自动 选择正确的。如果您希望每个精灵类型都不同,那么这就是您所需要的。

这就是 "object slicing" 成为问题的地方。 Sprite 可以作为指针或引用存在,但只能指向非抽象子class。所以 Sprite *Sprite sprite1 = new Sprite(); 不起作用,但 Sprite *Sprite sprite1 = new SpriteSubClass(); 起作用。

如果你让 Sprite::collide 不是纯虚拟的,Sprite 将不再是抽象的 class,你 可以 使方法 运行 来自 Sprite class 本身,你可以做到 Sprite *Sprite sprite1 = new Sprite();。但我非常怀疑你是否需要它,因为你只看 subclasses.

每当共享一个方法名称,但其行为因子class而不同时,它必须是虚拟的。