我编写了一个小库,为SDL2在C++20中生成包装器,然后再用它构建一个简单的电子游戏,并且正在寻找对代码的一些反馈!
我们的想法不是创建新的类(这将在稍后阶段和更高级别完成),而只是将SDL2函数封装到以下函数中:
unique_ptr.get() ),基本上,https://eb2.co/blog/2014/04/c-14-and-sdl2-managing-resources/更进一步,并且几乎自动创建包装器。
下面是库的代码(我还没有包装SDL的所有函数,只有一些函数出现在窗口中):
#pragma once
#include <SDL.h>
#include <stdexcept>
#include <memory>
namespace {
// Creates a deleter for unique_ptr from a function
template<auto FUN>
class Deleter {
public:
template<typename T>
constexpr void operator()(T *arg) const {
FUN(arg);
}
};
// Creates a deleter for unique_ptr which does nothing
class NoDeleter {
public:
template<typename T>
constexpr void operator()(T *arg) const {}
};
// Helper class to get a T* from a unique_ptr<T>, and itself from everything else
template<typename T>
class AsRaw {
public:
static constexpr auto call(auto &&t) {
return t;
}
};
template<typename T, typename D>
class AsRaw<std::unique_ptr<T, D> &> {
public:
static constexpr auto call(std::unique_ptr<T, D> &t) {
return t.get();
}
};
template<typename T, typename D>
class AsRaw<std::unique_ptr<T, D> &&> {
public:
static constexpr auto call(std::unique_ptr<T, D> &&t) {
return t.get();
}
};
template<typename T, typename D>
class AsRaw<const std::unique_ptr<T, D> &> {
public:
static constexpr auto call(const std::unique_ptr<T, D> &t) {
return t.get();
}
};
template<typename T>
auto as_raw(T &&t) {
return AsRaw<T>::call(std::forward<T>(t));
}
// Forward-declaration only, used for decltype to get the return type of a function pointer
template<typename OUT, typename ...INS>
OUT return_type(OUT (*)(INS...));
}
namespace sdl {
/**
* The wrapper
* @tparam FUN The function to wrap
* @tparam CHECK Whether to check the return value for errors (only used if the return type is not void)
*/
template<auto FUN, bool CHECK = true>
class Wrapper {
using out_t = decltype(return_type(FUN));
public:
constexpr void operator()(auto &&... arguments) const requires(std::is_void_v<out_t>) {
FUN(std::forward<decltype(as_raw(arguments))>(as_raw(arguments))...);
}
constexpr out_t operator()(auto &&... arguments) const requires (!CHECK &&
!std::is_void_v<out_t>) {
return FUN(std::forward<decltype(as_raw(arguments))>(as_raw(arguments))...);
}
constexpr out_t operator()(auto &&... arguments) const requires (CHECK &&
std::is_pointer_v<out_t>) {
auto res = FUN(std::forward<decltype(as_raw(arguments))>(as_raw(arguments))...);
if (res == nullptr) {
throw std::runtime_error(SDL_GetError());
}
return res;
}
constexpr out_t operator()(auto &&... arguments) const requires (CHECK &&
std::is_integral_v<out_t> &&
std::is_signed_v<out_t>) {
auto res = FUN(std::forward<decltype(as_raw(arguments))>(as_raw(arguments))...);
if (res < 0) {
throw std::runtime_error(SDL_GetError());
}
return res;
}
};
/**
* Secondary wrapper to change the return type of the function
* @tparam WRAPPER Wrapped function
* @tparam OUT The new return type
*/
template<typename WRAPPER, typename OUT>
class TypeWrapper {
public:
constexpr OUT operator()(auto &&... arguments) const {
return OUT{WRAPPER{}(std::forward<decltype(arguments)>(arguments)...)};
}
};
// The SDL Wrappers
constexpr Wrapper<SDL_Init> init;
using Window = std::unique_ptr<SDL_Window, Deleter<SDL_DestroyWindow>>;
constexpr TypeWrapper<Wrapper<SDL_CreateWindow, true>, Window> create_window;
using WindowSurface = std::unique_ptr<SDL_Surface, NoDeleter>;
constexpr TypeWrapper<Wrapper<SDL_GetWindowSurface>, WindowSurface> get_window_surface;
constexpr Wrapper<SDL_FillRect> fill_rect;
constexpr Wrapper<SDL_MapRGB, false> map_rgb;
constexpr Wrapper<SDL_UpdateWindowSurface> update_window_surface;
constexpr Wrapper<SDL_PollEvent, false> poll_event;
constexpr Wrapper<SDL_Quit> quit;
// Just a simple Context class to use RAII for init and quit
class Context {
public:
Context() {
sdl::init(SDL_INIT_VIDEO);
}
~Context() {
sdl::quit();
}
};
}以及创建窗口的代码:
#include "sdl.hh"
constexpr int SCREEN_WIDTH = 640;
constexpr int SCREEN_HEIGHT = 480;
int main() {
sdl::Context context;
auto window =
sdl::create_window("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
auto screenSurface = sdl::get_window_surface(window);
sdl::fill_rect(screenSurface, nullptr, sdl::map_rgb(screenSurface->format, 0xFF, 0xFF, 0xFF));
sdl::update_window_surface(window);
SDL_Event e;
bool quit = false;
while (!quit) {
while (sdl::poll_event(&e)) {
if (e.type == SDL_QUIT) quit = true;
}
}
return 0;
}发布于 2023-02-25 15:52:40
我不认为您如何实现删除器有什么问题,但是有两个备选方案您可以考虑。首先,您可以创建std::default_delete的自定义专门化,因此不需要向std::unique_ptr传递第二个模板参数:
namespace std {
template<>
struct default_delete<SDL_Window> {
void operator()(SDL_Window *window) const {
SDL_DestroyWindow(window);
}
};
}
namespace sdl {
using Window = std::unique_ptr<SDL_Window>;
…
};不过,这并不能节省很多代码。另一种选择是使用lambdas的功能完全编写自定义删除类。考虑:
template<auto FUN>
using Deleter = decltype([](auto* arg){FUN(arg);});
using NoDeleter = decltype([](auto*){});或者您甚至可以将lambda类型直接传递给std::unique_ptr:
using Window = std::unique_ptr<SDL_Window, decltype([](auto* w){SDL_DestroyWindow(w);})>;虽然保留Deleter和NoDeleter类型可能更好,但它使SDL对象类型的声明更加清晰。
在Wrapper中有一些代码重复,您可能可以减少。例如,您可以使用if constexpr来检查是否需要检查返回值,如果需要检查返回值的方法,而不是使用模板重载。
AsRaw的所有重载吗?因为SDL是一个C库,所以它没有rvalue的概念,所以在AsRaw中对它进行重载似乎是没有意义的。
AsRaw可能会泄露太多的AsRaw将打开所有的std::unique_ptrs,甚至那些不是由您的类型别名创建的。考虑到一些SDL函数采用void*参数,可能会有人传递自己的std::unique_ptr,而现在它将被不正确地打开。虽然它很可能是一个bug,但是现在您已经导致编译器不再捕获该bug了。
为此,您可能希望为Window和WindowSurface创建不同的类型,而不是为std::unique_ptr键入别名,然后AsRaw可以专门处理这些类型。
https://codereview.stackexchange.com/questions/283565
复制相似问题