【问题标题】:Good practice for error handling and cleaning up resources in SDL?SDL 中错误处理和清理资源的良好做法?
【发布时间】:2020-06-27 17:06:17
【问题描述】:

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

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(或其他智能指针)并提供删除器类进行清理。
    • 使用atexit()处理SDL_Quit()IMG_Quit()?

我正在考虑实现一个包装类,但后来意识到我可能需要抛出异常以防初始化期间出现错误,例如来自SDL_CreateWindow(),我试图避免。有什么帮助或建议吗?

【问题讨论】:

  • 您的进程即将消失 - 何必呢?内核对象/句柄将作为进程终止的一部分关闭。还剩下什么?

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


【解决方案1】:
  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. 按照 cmets 中的建议,考虑在程序退出之前不要清理使用的资源。

对于在程序退出之前使用的资源,不需要清理。操作系统会自动清理它们。 (SDL_QuitSDL_DestroyWindow等)


一般来说,在使用 SDL 时我们应该在什么时候引发异常?除非需要,否则通常避免使用它们是否被认为是一种好习惯?

如果您正确地进行清理(使用析构函数和/或范围保护,而不是手动),则异常将使这方面的事情变得更容易,而不是更难。

在您真正投掷之前,它们不应该对性能产生太大影响,所以如果您只在极少数情况下投掷应该没问题。但它们确实会增加二进制文件的大小。

【讨论】:

  • 在创建包装类时,您是否说为 SDL 和 SDL_IMG 初始化也创建一个是个好主意(包装 SDL 环境初始化感觉有点奇怪)?
  • @PremsupapongVanitcharenthum 这取决于你。在我看来并没有错。
猜你喜欢
  • 1970-01-01
  • 2010-09-14
  • 2010-09-18
  • 1970-01-01
  • 2012-01-15
  • 2013-02-14
  • 2016-12-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多