【问题标题】:C++ passing a list of a class with elements of a subclassC ++传递具有子类元素的类列表
【发布时间】:2015-01-05 04:56:14
【问题描述】:

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

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

该方法效果很好。我的问题出现在以下方面,我已经实现了不同的类,它们是“Sprite”的子类,并且将代表我游戏中的敌人,因此,例如我会有对象:敌人 1 类:公共 Sprite,敌人 2 类:公共 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 会检测 sprite 和 sprite 列表之间的碰撞,而 group_group_collide 会检测 sprite 组(两个不同的列表)之间的碰撞。出现的问题是:至少有 4 种敌人,它们都是我的 Sprite 类的子类,但是当我创建精灵列表并添加精灵子类的元素时出现编译错误。我的解决方案是为所有类型的敌人编写一个 group_collide 和 group_group collide 方法,但这很不雅。有没有更好的方法来解决这个问题?

编辑:

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

std::list<Sprite*> enemy_group;

例如,我以这种方式添加作为精灵子类的“Kamikaze”类的元素(该类中的方法更新不同):

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

方法 update 是从 Sprite 类调用的,而不是 Kamikaze 类,因此我也有这种方法的对象切片问题,也许我所做的有问题?

【问题讨论】:

标签: c++ list variables inheritance subclass


【解决方案1】:

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

所有派生类都将被“切片”以适合对象容器,例如 std::list。 std::list&lt;Sprite*&gt; //(Preferably a smart pointer) 将避免此问题,但实际对象必须存储在其他地方。

【讨论】:

  • 感谢您的回答。我将尝试您的建议。
【解决方案2】:
  1. 根据 Laserbreath 的回答,您应该将 std::list&lt;Sprite&gt; 替换为 std::list&lt;Sprite*&gt;。目前,任何通过的子类都将简化为 Sprite。使用指针列表可以避免这种情况。

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

在基类中:

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

在子类中

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

所以,当 collide 方法被 Sprite pointerreference 调用时,调用将被重定向到指定的子类的函数。

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,程序自动选择正确的。如果您希望每种精灵类型都不同,那么这就是您所需要的。

这就是“对象切片”成为问题的地方。 Sprite 可以作为指针或引用存在,但仅限于非抽象子类。所以Sprite *Sprite sprite1 = new Sprite(); 不起作用,但Sprite *Sprite sprite1 = new SpriteSubClass(); 起作用。

如果你让Sprite::collide 不是纯虚拟的,Sprite 将不再是一个抽象类,你可以让方法从 Sprite 类本身运行,你可以做Sprite *Sprite sprite1 = new Sprite();。但我非常怀疑您是否需要它,因为您只查看子类。

只要方法名是共享的,但其行为因子类而异,则它必须是虚拟的。

【讨论】:

  • 如果碰撞对不同的精灵有不同的作用,那么只为一种类型调用它是行不通的。他们需要互相调用,因为这是常见的行为,它在基类中,所以其他一些函数将是虚拟的(比如 get_bounding_points 可能)。
  • 使用纯虚函数自动选择所需的方法。如果方法是纯虚拟的,它不会进入基类,因为它的行为在基类中是未定义的。您只需从 Sprite* 或 Sprite& 调用它,它就会从正确的子类调用碰撞。我更新了我的答案,所以看看这个例子。如果我正确理解您的问题,这正是您所需要的。
  • 你的回答很完美,它帮助了我。我在定义神风敢死队类的更新方法时遇到了一些问题,不是从超类“重载”该方法,而是按照您的解释,我做对了!谢谢;)
猜你喜欢
  • 1970-01-01
  • 2011-05-11
  • 1970-01-01
  • 2021-03-10
  • 1970-01-01
  • 1970-01-01
  • 2017-01-21
  • 1970-01-01
  • 2021-11-26
相关资源
最近更新 更多