错误处理和清理SDL中资源的良好实践?

时间:2020-06-27 17:06:17

标签: c++ error-handling sdl code-cleanup

使用SDL时,我一直在通过在函数中遇到错误时返回false来处理错误(例如,在SDL初始化期间),否则返回true。然后通过在close()函数的结尾处调用main()函数来执行清理操作,例如:

int main(){
    if(!init()){
        // do some stuff
    }

    ...

    close();
    return 0;
}

close()功能:

void close(){
    SDL_DestroyRenderer(g_Renderer);
    SDL_DestroyWindow(g_Window); 

    IMG_Quit();
    SDL_Quit();
}

但是,我已经意识到这种清除方法意味着我无法真正抛出异常,因为不会调用close()函数。 经过一番阅读之后,我决定出于性能相关的原因,除非有必要,否则我将尽量不要抛出异常(我听说这很重要,尤其是在游戏开发中)。

此外,这意味着我需要随着程序的增长在close()上添加更多清理功能,这似乎不切实际,容易忘记。

所以我的问题是:

  1. 通常,使用SDL时应何时引发异常?除非必要,通常避免使用它们是否被认为是一种好习惯?
  2. 为了允许使用异常,应如何清理SDL资源?我已经研究了一些方法,但是不确定何时使用每种方法或将每种方法组合使用:
    • 创建一个C ++包装类,以处理构造函数中的初始化和析构函数中的清理
    • 通过正常初始化SDL对象来使用unique_ptr(或其他智能指针),并提供用于清除的Deleter类。
    • 使用atexit()处理SDL_Quit()IMG_Quit()吗?

我当时正在考虑实现一个包装器类,但后来意识到我可能需要在初始化期间发生错误的情况下引发异常,例如来自SDL_CreateWindow(),我正努力避免。有任何帮助或建议吗?

1 个答案:

答案 0 :(得分:2)

  1. 使用atexit()处理SDL_Quit()IMG_Quit()吗?

我不会使用atexit。其他替代方法使您可以更好地控制何时进行清理。

  1. 通过正常初始化SDL对象来使用unique_ptr(或其他智能指针),并提供清除程序的删除类。

它可以工作,但是我不喜欢这种方法。它可以清理SDL_Window *之类的指针,但是如果您需要清理的东西不是指针怎么办? (例如,如果您想自动呼叫SDL_Quit()。)

使用unique_ptr清除指针和其他清除其他内容的方法会在没有充分理由的情况下将不一致引入到您的代码中,因此我会完全避免这种情况(内存管理除外)。

  1. 创建一个C ++包装器类,以处理构造函数中的初始化和析构函数中的清理

这是个好主意。

如果您要这样做,请不要忘记遵循the rule of three。删除复制构造函数和赋值运算符:

MyWindow(const MyWindow &) = delete;
MyWindow &operator=(const MyWindow &) = delete;

或者使类可移动且不可复制,以使其易于使用。

  1. 护卫队。

还有另一种选择:护目镜。想法是将清除代码放入局部变量的析构函数中。

这些局部变量通常是使用宏创建的。这就是我用的:

#include <exception>
#include <utility>

namespace Macro
{
    template <typename T> class FinallyObject
    {
        T func;

      public:
        FinallyObject(T &&func) : func(std::move(func)) {}
        FinallyObject(const FinallyObject &) = delete;
        FinallyObject &operator=(const FinallyObject &) = delete;
        ~FinallyObject()
        {
            func();
        }
    };
}

#define FINALLY_impl_cat(a, b) FINALLY_impl_cat_(a, b)
#define FINALLY_impl_cat_(a, b) a##b

#define FINALLY(...) \
    ::Macro::FinallyObject FINALLY_impl_cat(_finally_object_,__LINE__) ([&]{ __VA_ARGS__ });

这是它们的使用方式:

int main(int, char **)
{
    if (SDL_Init(...))
        /*throw or exit with an error*/;
    FINALLY( SDL_Quit(); )

    SDL_Window *window = SDL_CreateWindow(...);
    if (!window)
        /*throw or exit with an error*/;
    FINALLY( SDL_DestroyWindow(window); )

    // Your code here.
}
  1. 如注释中所建议,请考虑在程序退出之前不要清除使用的资源。

对于在程序退出之前使用的资源,则不必清除。操作系统将自动清理它们。 ({SDL_QuitSDL_DestroyWindow等)


通常,使用SDL时应何时引发异常?除非必要,通常避免使用它们是否被认为是一种好习惯?

如果您正确地进行了清理(使用析构函数和/或范围保护,而不是手动进行清理),那么异常将使这方面的工作变得简单而不困难。

在实际抛出之前,它们不会对性能造成太大影响,因此,如果仅在极少数情况下抛出,则应该没问题。但是它们确实增加了二进制文件的大小。

相关问题