SDL 为什么在堆而不是栈上创建纹理

SDL why create textures on the heap instead of stack

所以我一直在阅读 lazyfoos SDL2 教程,我很好奇为什么所有 SDL_Surface/SDL_textures 等都是在堆上创建的?我意识到 SDL 本身是用 C 和函数 accept/return 指针编写的,但为什么不在堆栈上创建表面并将其引用传递给函数呢? (或在分配时取消引用)

希望我已经说清楚了。这里有一个代码片段来进一步尝试解释:

为什么这样:

void pointer()
{
    SDL_Surface *windowsurface;
    SDL_Surface *surface = SDL_LoadBMP("asdf.bmp");
    SDL_BlitSurface(surface, 0, windowsurface, 0);
}

而不是这个:

void stack()
{
    SDL_Surface windowsurface;
    SDL_Surface surface = *(SDL_LoadBMP("asdf.bmp"));
    SDL_Blit(&surface, 0, &windowsurface, 0);
}

如果后者确实可行,还需要调用SDL_FreeSurface吗?如果是这样,这将确保您不会泄漏任何内存,所以我不太清楚前者的好处。

希望这一切都有意义,我在 SDL 方面还是个菜鸟

我也更喜欢在不必要的时候避免使用指针,但是许多游戏程序员在不需要的时候使用指针,用 new 分配几乎任何大的东西,使其成为全局的(!),并在最后调用 delete。

使用动态分配有一个小优势(超过了指针错误的风险,IMJ)。当你在两个对象之间分配时,你需要一个复制构造函数,这意味着你必须决定是否共享内存。这通常是个坏主意,如果你不这样做,你的复制 ctor 可能需要做很多工作(而复制指针非常便宜)。更高版本的 C++ 避免了部分问题,但不是全部。

但是在 SDL 中,SDL 的选择确实迫使您不仅使用 delete 或 free 解除分配,还使用其特殊功能 SDL_FreeSurfaceSDL_DestroyTexture 等。对于使用此类的结构功能,你只是卡住了。因此,SDL 程序员动态分配这些东西,即使他们不是认为眼前的一切都应该成为指针的铁杆游戏程序员。您可以编写包装器来稍微清理一下,但在底层,您仍然受困于它们的删除例程。

这种方法存在一些问题:

void stack()
{
    SDL_Surface windowsurface;
    SDL_Surface surface = *(SDL_LoadBMP("asdf.bmp"));
    SDL_Blit(&surface, 0, &windowsurface, 0);
}

首先,对象 windowsurfacesurface 将在函数退出时被销毁,因为它们创建的堆栈部分将被调用函数回收。

其次,当它们被销毁时,将通过调用等效的 delete 来完成,这可能不是这些对象的正确删除器。该库提供了自己需要调用的删除函数。

最后,位图对象的 surface 赋值可能会很慢,因为位图可能很大。分配指针要快得多。

管理从空闲存储(堆)分配的对象的一种安全方法是使用智能指针:

// create a special deleter that calls the correct function
// to delete the object
struct SDL_Surface_deleter
{
    void operator()(SDL_Surface* surface) const { SDL_FreeSurface(surface); }
};

// some type aliases for convenience
using SDL_Surface_uptr = std::unique_ptr<SDL_Surface, SDL_Surface_deleter>;
using SDL_Surface_sptr = std::shared_ptr<SDL_Surface>;

SDL_Surface_uptr make_unique_surface(SDL_Surface* surface)
{
    return SDL_Surface_uptr{surface};
}

SDL_Surface_sptr make_shared_surface(SDL_Surface* surface)
{
    return {surface, SDL_Surface_deleter{}};
}

void smart_objects()
{
    // no need to delete this
    auto unique_surface = make_unique_surface(SDL_LoadBMP("asdf.bmp"));

    // no need to delete this either
    auto shared_surface = make_shared_surface(SDL_LoadBMP("asdf.bmp"));

    // stuff...
}

查找std::unique_ptr and std::shared_ptr in The Manual.

这有很多原因,但我只关注一个。具体来说,在 C++ 中,没有可移植的方法来在编译时在堆栈上创建大小未知的对象。

这是一个严重的问题,因为编译器不知道纹理的大小,因为它不知道加载到其中的数据有多大。在 C 中,这可以通过使用 VLA 有所帮助,但是由于许多操作系统上可用的堆栈大小较小,因此对于大型对象仍然不可取。

除此之外,纹理可以在 GPU 内存而不是主内存中实现。这绝对不能在堆栈上,必须由正在使用的任何系统中的图形例程管理。这些图形系统通常只提供一个不透明的指针,指向 GPU 内存中的纹理,可以使用提供的例程释放或管理它。

现在你可能会争辩说句柄结构至少可以存在于堆栈中,但实际上这提供了最小的节省,因为绝大多数读写将针对纹理本身而不是句柄对象,因此优化没有什么价值。