我如何为 SDL2 装箱不同的 "Resource" 指针?

How Do I Box Disparate "Resource" Pointers for SDL2?

想实现一个资源加载器,概念上感觉SDL2里的所有资源都是一样的;完成后需要释放资源,SDL_Texture*SDL_DestroyTextureMix_Music*Mix_FreeMusicMix_Chunk*Mix_FreeChunkTTF_Font* TTF_CloseFont。所有的变化都是“删除器”函数的名称,所以我想把这些全部装箱,这样我就不需要为每种类型的资源使用 4 个不同的 std::maps。

我已经实现了一个小的 class 来装箱数据,但是在使用泛型时我无法恢复类型。具体来说,当我尝试通过 value.get<SDL_Texture*>()

void* 转换回 SDL_Texture* 时,我得到“SDL_Texture* 是一个不完整的类型”

ValueBox.h

    // helper class used to box various pointers for sdl, like textures, chunks, fonts, etc
    class ValueBox {
    public:
        std::function<void(void)> clean;
        void* data;
    
        ValueBox(void* data, std::function<void(void)> clean) : data(data), clean( std::move(clean) ) {}
        ~ValueBox() {
            clean();
        }
    
        template<typename T>
        T get() {
            return dynamic_cast<T>(data);
        }
    };

如何实现 class 允许我将指针装箱,这样我就不需要在加载程序中使用四个不同的地图? (还是我做了不该做的事?)

dynamic_cast 只有在从多态类型转换为多态类型(或 void*)时才有意义。多态类型是具有(可能继承)至少一个虚函数的 class(或 struct,形式上也是 class)。

None 您列出的类型是多态的(因为它们来自 C 库,而 C 没有虚函数)。但即使它们是,void 本身也不是多态的,所以它无论如何也行不通。

此外,您必须 dynamic_cast 指向指针或引用,但由于上述原因,这并不重要。


由于您所有的资源都是指针,您可以[ab]使用std::unique_ptr 来自动处理它们。这是 FILE *std::fopen 的示例:

using file_ptr = std::unique_ptr<FILE, std::integral_constant<decltype(&std::fclose), std::fclose>>;

int main()
{
    file_ptr f(std::fopen("foo.txt", "rb")); // This is closed automatically.
}

但是,我不建议这样做,因为它只适用于指针。如果您遇到一种不是指针的新型资源,您将不得不以不同的方式管理它,从而使您的代码不一致。

理论上你可以写一个类似于std::unique_ptr的class,不限于指针,但在我自己尝试之后,我认为这不是很方便,不值得努力。

我建议为每种资源编写一个单独的 class,使用以下模式:

class FilePtr
{
    FILE* file = nullptr;

  public:
    FilePtr() {} // Optional

    FilePtr(const char *filename, const char *mode) // Change parameters as needed.
    {
        file = std::fopen(filename, mode);
        if (!file)
            throw std::runtime_error("Can't open file!");
    }

    FilePtr(FilePtr &&other) noexcept : file(std::exchange(other.file, {})) {}
    FilePtr &operator=(FilePtr other) noexcept
    {
        std::swap(file, other.file);
        return *this;
    }

    ~FilePtr()
    {
        if (file)
            std::fclose(file);
    )

    [[nodiscard]] explicit operator bool() const {return bool(file);} // Optional.

    // Add more functions as needed.
};

由于这些包装器非常简单,您可以轻松地为每种资源编写它们。

拥有单独的 classes 还允许您向它们添加特定于资源的功能。


class that allows me to box the pointers so that I don't need four different maps in the loader?

我会使用不同的地图。这意味着您不需要在运行时验证资源类型,这意味着故障点少了一个。

但是无论地图的数量如何,您都可以从单个基础继承包装器 classes,以减少代码重复。

如果您使基础多态化,您将能够将资源存储在到该基础的 shared_ptrs(或 unique_ptrs)的单个映射中。