我如何为 SDL2 装箱不同的 "Resource" 指针?
How Do I Box Disparate "Resource" Pointers for SDL2?
想实现一个资源加载器,概念上感觉SDL2里的所有资源都是一样的;完成后需要释放资源,SDL_Texture*
与 SDL_DestroyTexture
,Mix_Music*
与 Mix_FreeMusic
,Mix_Chunk*
与 Mix_FreeChunk
,TTF_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_ptr
s(或 unique_ptr
s)的单个映射中。
想实现一个资源加载器,概念上感觉SDL2里的所有资源都是一样的;完成后需要释放资源,SDL_Texture*
与 SDL_DestroyTexture
,Mix_Music*
与 Mix_FreeMusic
,Mix_Chunk*
与 Mix_FreeChunk
,TTF_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_ptr
s(或 unique_ptr
s)的单个映射中。