push_back(*obj) 正在调用析构函数并导致 SDL_BlitSurface 崩溃

push_back(*obj) is calling destructor and is causing SDL_BlitSurface to crash

我是 C++ 中 SDL 编程的新手,所以我正在开发一个简单的 Invaders 游戏。

里面我有几个classes,其中之一当然是外星人class。 Alien 的析构函数调用 SDL_FreeSurface(SPRITEOFALIEN),以删除对象。

我在游戏中也有一个矢量 class,如果只有一个外星人,一切都很好,但是我一生成另一个外星人,我就调用 push_back 将其添加到向量中,析构函数被调用,因此当涉及到实际绘制外星人的向量时,SDL_BlitSurface 使程序崩溃。

示例代码:

vector<Alien> aliens;

// More code.... and eventually

if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
    alien = new Alien(150, 0); // Alien* alien 
    aliens.push_back(*alien);
}
// Some more code... advancing the aliens, etc...

std::vector<Alien>::iterator it2;
for (it2 = aliens.begin(); it2 != aliens.end(); ++it2)
    SDL_BlitSurface(it2->getSprite(), 0, screen, it2->getRect()); /// CRASH, FIXME

我查了其他类似的问题,但他们都建议有一个固定大小的向量,我认为这对我的情况来说不是一个好的解决方案,因为应该有任意数量的外星人(还有导弹) ,我对他们也有同样的问题。

我也知道它发生是因为当调用 push_back 时,向量被复制到一个新的更大的向量,这就是调用析构函数的原因。然而由于某种原因,drawObjects 函数捕获了 OLD 向量并崩溃了...

有办法解决这个问题吗?

外星人Class定义:

#include "alien.h"

Alien::Alien(int x, int y)
{
    rect.x = x; // x-pos
    rect.y = y; // y-pos
    sprite = SDL_LoadBMP("alien.bmp");

}

Alien::~Alien()
{
    SDL_FreeSurface(sprite)
    printf("Deleted Alien\n");
}


void Alien::move()
{

    rect.y += SPEED;
}

很可能 Alien class 使用 new 分配一些指针,在销毁时释放它们并且没有复制构造函数。在那种情况下,默认实现是复制指针,而不是分配对象的新副本。您应该为 Alien 编写一个复制构造函数来精确地执行此操作。如果你想象 memberAlien(class Member)的唯一成员,那看起来像这样:

Alien(const Alien & a) {
  member = new Member(*member);
}

当然,如果反过来Member分配一些对象,你也应该在那里创建正确的复制构造函数。

首先,您在复制对象时遇到的问题很可能是您破坏了 "rule of three" -http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)

如果你的对象不能被复制,你应该禁止复制 and/or 移动(例如使 copy/move ctor 私有)至少编译器会告诉你是否有问题并且你尝试复制他们。

现在,为了避免在向量中复制对象,您需要在向量中保留足够的 space 并在创建时调用 emplace(如果您可以使用 C++11)。或者你应该通过指针来保存对象。您动态创建实例的代码建议您或其他人计划这样做,但出于任何原因将其按值放入 vector<Alien> 中。此外,您不应该通过 vector 中的原始指针保留对象(除非您有足够的经验并且知道自己在做什么)所以您应该使用智能指针,如果您使用 C++11 或来自 boost 或类似库的标准否则:

typedef std::shared_ptr<Alien> AlienPtr;
typedef std::vector<AlientPtr> Aliens;
Aliens aliens;

if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
    aliens.push_back( std::make_shared<Alien>( 150, 0 ) );
}

向量大小不同的根本问题是,当大小增加时,向量可能必须重新分配并将其所有内容放在新位置。这将复制所有现有内容,然后销毁旧对象。

由于您不希望您的对象被破坏(清理 SDL 表面),我们需要安排向量复制并破坏其他东西。它可能是原始指针,但更好的方法是使用智能指针,例如 std::unique_ptr.

试试这个:

std::vector<std::unique_ptr<Alien>> aliens;

...

if (event.key.keysym.sym == SDLK_a) //Add Alien! Debugging
{
    alien = new Alien(150, 0); // Alien* alien 
    aliens.emplace_back(alien);
}

然后当你使用它时,你需要取消引用它...

for (std::unique_ptr<Alien>& that_alien : aliens)
    SDL_BlitSurface(that_alien->getSprite(), 0, screen, that_alien->getRect());

这避免了使用损坏的 Alien 复制构造函数。您应该更进一步并禁用它以确保它不会被意外调用。在您的 class 定义中,添加:

class Alien
{
// add one of the two lines below
    private: Alien(const Alien&) = delete; // if your compiler has = delete implemented
    private: Alien(const Alien&); // this is almost as good
};

您的问题可能来自 Alien 的副本。当副本被破坏时,它将破坏所有副本共享的表面资源。如果在您调用 push_back() 时制作了多个副本——我敢打赌至少有两个副本——那么仅在这一行上,您就双重释放了 SDL_Surface *,这很可能会导致崩溃.

我强烈建议定义一个类型专门 以 RAII 方式管理表面,然后使用 Alien 中的类型,因为您将获得正确的语义默认。

例如:

struct SDLSurfaceDeleter
{
    void operator()(SDL_Surface * p) const
    {
        if (p) { SDL_FreeSurface(p); }
    }
};

typedef std::unique_ptr<SDL_Surface, SDLSurfaceDeleter> UniqueSDLSurface;

现在您的 Alien class 您有:

private:
    UniqueSDLSurface sprite;

在你的构造函数中:

sprite = UniqueSDLSurface(SDL_LoadBMP("alien.bmp"));

(或者,更好的是,在初始化列表中初始化它:sprite(SDL_LoadBMP("alien.bmp"))。使用初始化列表方法,您可以在第一时间正确构造 UniqueSDLSurface 对象;使用赋值方法,您默认-构造它然后移动分配一个新对象。两者都可以,但初始化列表方法更干净。)

最后,删除 Alien 析构函数。现在,Alien class 应该可以自动移动,但不能复制。如果您在复制 Alien 对象时遇到任何编译时错误,您需要修复这些错误;它们首先是您问题的根源,使用这段代码,编译器 不再让复制发生 ,这是一件好事!

在适当的 C++ 标准库中,std::vector 将在重新分配期间尽可能移动对象,因此现在您将能够正确使用它,因为它会将您的 Alien 对象移动到他们的新目的地,而不是复制他们。

作为旁注,您可以类似地使用 std::shared_ptr 创建一个共享表面,当最后一个 std::shared_ptr 被销毁时,该表面将被释放。这在这里可能更有意义,因为所有 Alien 对象都使用相同的源位图;您可以加载一次,然后在所有实例之间共享它:

std::shared_ptr<SDL_Surface> make_shared_surface(SDL_Surface * surface)
{
    return std::shared_ptr<SDL_Surface>(surface, SDLSurfaceDeleter());
}

此外,这里你泄漏了内存,因为你没有 delete 你用 new:

分配的对象
alien = new Alien(150, 0); // Alien* alien 
aliens.push_back(*alien);

但是你不需要这样做,因为你可以这样做:

aliens.push_back(Alien(150, 0));

或者,更好的是:

aliens.emplace_back(150, 0);