我是C ++中SDL编程的新手,所以我正在开发一个简单的Invaders游戏。
在其中我有几个课程,其中一个课程当然是外星人课程。 Alien的析构函数需要 SDL_FreeSurface(SPRITEOFALIEN),以便删除该对象。
我在Game类中也有一个向量,如果只有一个Alien,然而,只要我产生另一个Alien并且我调用 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向量并崩溃......
有解决方法吗?
外星人阶级定义:
#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;
}
答案 0 :(得分:4)
您的问题可能来自Alien
的副本。当一个副本被破坏时,它将破坏所有副本共享的表面资源。如果您在致电push_back()
时正在制作多份副本 - 我敢打赌至少有两份副本 - 那么仅在这一行就可以双倍释放SDL_Surface *
这可能会导致崩溃
我强烈建议定义一个特定类型来以RAII方式管理表面,然后使用Alien
中的表面,因为您将默认获得正确的语义。 / p>
例如:
struct SDLSurfaceDeleter
{
void operator()(SDL_Surface * p) const
{
if (p) { SDL_FreeSurface(p); }
}
};
typedef std::unique_ptr<SDL_Surface, SDLSurfaceDeleter> UniqueSDLSurface;
现在你的Alien
课程中有:
private:
UniqueSDLSurface sprite;
在你的构造函数中:
sprite = UniqueSDLSurface(SDL_LoadBMP("alien.bmp"));
(或者,更好的是,在初始化列表中初始化它:sprite(SDL_LoadBMP("alien.bmp"))
。使用初始化列表方法,您可以在第一次构建UniqueSDLSurface
对象;使用赋值方法,您可以默认构造它然后移动 - 分配一个新对象。两者都可以,但初始化列表方法更清晰。)
最后,删除Alien
析构函数。现在,Alien
类应该可以自动移动,但不能复制。如果您在复制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);
答案 1 :(得分:2)
首先,当你的对象被复制时你有问题这个事实很可能是你打破了三个&#34;规则。 - http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)
如果您的对象无法复制,您应该禁止复制和/或移动(例如复制/移动ctor私有)至少然后编译器会告诉您是否有问题并且您尝试复制它们。
现在要避免在向量中复制对象,您需要在向量中保留足够的空间,并在创建时调用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 ) );
}
答案 2 :(得分:2)
不同大小的向量的基本问题是,当大小增加时,向量可能必须重新分配并将其所有内容放在新位置。这将复制所有现有内容,然后销毁旧对象。
由于你不希望你的对象被破坏(清理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 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
};
答案 3 :(得分:0)
很可能Alien
类使用new分配一些指针,在销毁时释放它们并且没有复制构造函数。在这种情况下,默认实现是复制指针,而不是分配对象的新副本。您应该为Alien
编写一个完全正确的复制构造函数。如果您认为member
是Alien
(类Member
)的唯一成员,那就是这样的:
Alien(const Alien & a) {
member = new Member(*member);
}
当然,如果依次Member
分配一些对象,你也应该在那里制作正确的拷贝构造函数。