首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SDL简单包装库

SDL简单包装库
EN

Code Review用户
提问于 2023-02-25 02:47:29
回答 1查看 136关注 0票数 6

我编写了一个小库,为SDL2在C++20中生成包装器,然后再用它构建一个简单的电子游戏,并且正在寻找对代码的一些反馈!

我们的想法不是创建新的类(这将在稍后阶段和更高级别完成),而只是将SDL2函数封装到以下函数中:

  • 检查错误,
  • 返回唯一的指针来自动释放资源,
  • 与文档中的调用完全相同(我不想在调用窗口上的函数时执行unique_ptr.get() ),
  • 使用返回void、指针、错误代码(0:成功、<0: error)或整数的函数。

基本上,https://eb2.co/blog/2014/04/c-14-and-sdl2-managing-resources/更进一步,并且几乎自动创建包装器。

下面是库的代码(我还没有包装SDL的所有函数,只有一些函数出现在窗口中):

代码语言:javascript
复制
#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();
        }
    };
}

以及创建窗口的代码:

代码语言:javascript
复制
#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;
}
EN

回答 1

Code Review用户

发布于 2023-02-25 15:52:40

的事情,你可以做不同的

我不认为您如何实现删除器有什么问题,但是有两个备选方案您可以考虑。首先,您可以创建std::default_delete的自定义专门化,因此不需要向std::unique_ptr传递第二个模板参数:

代码语言:javascript
复制
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的功能完全编写自定义删除类。考虑:

代码语言:javascript
复制
template<auto FUN>
using Deleter = decltype([](auto* arg){FUN(arg);});
using NoDeleter = decltype([](auto*){});

或者您甚至可以将lambda类型直接传递给std::unique_ptr

代码语言:javascript
复制
using Window = std::unique_ptr<SDL_Window, decltype([](auto* w){SDL_DestroyWindow(w);})>;

虽然保留DeleterNoDeleter类型可能更好,但它使SDL对象类型的声明更加清晰。

避免代码重复

Wrapper中有一些代码重复,您可能可以减少。例如,您可以使用if constexpr来检查是否需要检查返回值,如果需要检查返回值的方法,而不是使用模板重载。

你需要AsRaw的所有重载吗?

因为SDL是一个C库,所以它没有rvalue的概念,所以在AsRaw中对它进行重载似乎是没有意义的。

AsRaw可能会泄露太多的

AsRaw将打开所有的std::unique_ptrs,甚至那些不是由您的类型别名创建的。考虑到一些SDL函数采用void*参数,可能会有人传递自己的std::unique_ptr,而现在它将被不正确地打开。虽然它很可能是一个bug,但是现在您已经导致编译器不再捕获该bug了。

为此,您可能希望为WindowWindowSurface创建不同的类型,而不是为std::unique_ptr键入别名,然后AsRaw可以专门处理这些类型。

票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/283565

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档