SDL类型的智能指针包装是否安全?

时间:2016-05-12 16:19:54

标签: c++ c++11 sdl smart-pointers

我考虑使用类似下面的内容,所以我不必记得在方法结束时明确调用驱逐舰函数:

#include <iostream>
#include <SDL2/SDL.h>
#include <memory>

int main()
{
    SDL_Init(SDL_INIT_VIDEO);

    std::unique_ptr<SDL_Window, decltype((SDL_DestroyWindow))>
        win { SDL_CreateWindow("asdf", 100, 100, 640, 480, SDL_WINDOW_SHOWN),
                  SDL_DestroyWindow };
    if (!win.get())
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow Error: %s",
                SDL_GetError());
        return 1;
    }

    SDL_Quit();
}

我不确定它是否是最佳方法。我担心即使看起来很简单,这并不能做我想做的事情。这种方法有什么微妙的错误吗?

5 个答案:

答案 0 :(得分:3)

介绍一个新的范围,你应该没问题:

BigtableIO

答案 1 :(得分:2)

更多地使用RAII:

struct SDL_RAII
{
    SDL_RAII() { SDL_Init(SDL_INIT_VIDEO); }

    ~SDL_RAII() noexcept {
        try {
            SDL_Quit();
        } catch (...) {
            // Handle error
        }
    }

    SDL_RAII(const SDL_RAII&) = delete;
    SDL_RAII(SDL_RAII&&) = delete;
    SDL_RAII& operator=(const SDL_RAII&) = delete;
    SDL_RAII& operator=(SDL_RAII&&) = delete;
};

并通过分解删除器来进行DRY:

template <typename Object, void (*DeleterFun)(Object*)>
struct Deleter
{
    void operator() (Object* obj) const noexcept
    {
        try {
            DeleterFun(obj);
        } catch (...) {
            // Handle error
        }
    }
};

template <typename Object, void (*DeleterFun)(Object*)>
using UniquePtr = std::unique_ptr<Object, Deleter<Object, DeleterFun>>;

然后,SDL的一些类型:

using Unique_SDL_Window = UniquePtr<SDL_Window, SDL_DestroyWindow>;
using Unique_SDL_Surface = UniquePtr<SDL_Surface, SDL_FreeSurface>;
// ...

最后:

int main()
{
    SDL_RAII SDL_raii;

    Unique_SDL_Window win{ SDL_CreateWindow("asdf", 100, 100, 640, 480, SDL_WINDOW_SHOWN)};

    if (!win.get())
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                     "SDL_CreateWindow Error: %s",
                     SDL_GetError());
        return 1;
    }
    return 0;
}

答案 2 :(得分:1)

如果你想朝着制作它的方向前进,那就看看&amp;感觉像现代C ++&#34;,您可以结合以下两种技术:

  • 范围保护(适用于SDL_Quit),
  • 专门的默认删除器(适用于SDL_Window)。

范围保护

范围保护是一个虚拟对象,它从析构函数中调用提供的函数对象。在堆栈上分配它将使它在离开定义范围时调用析构函数;在main()函数中执行此操作意味着它将在程序退出时运行。有关详细信息,请参阅nice talk by Andrei Alexandrescu(有关范围保护的部分从1:05:14开始)。

实施(主要来自演示文稿):

template<class Fn>
class ScopeGuard {
 public:
  explicit ScopeGuard(Fn fn) noexcept
      : fn_(std::move(fn)),
        active_(true) {}

  ScopeGuard() = delete;
  ScopeGuard(const ScopeGuard &) = delete;
  ScopeGuard(ScopeGuard &&that) noexcept
      : fn_(std::move(that.fn_)),
        active_(that.active_) {
    that.dismiss();
  }

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

  ~ScopeGuard() {
    if (active_) {
      try {
        fn_();
      } catch (...) {
        // The destructor must not throw.
      }
    }
  }

  void dismiss() noexcept {
    active_ = false;
  }

 private:
  Fn fn_;
  bool active_;
};

直接实例化这个类不是很方便,但在函数上我们得到类型推断:

// Provided purely for type inference.
template<class Fn>
ScopeGuard<Fn> scopeGuard(Fn fn) {
  return ScopeGuard<Fn>(std::move(fn));
}

要创建范围保护,您只需调用scopeGuard(lambda),其中lambda是您要在离开范围时运行的功能。推断实际类型;无论如何,我们对此并不感兴趣。

// Will call SDL_Quit() once 'guard' goes out of scope.
auto guard = scopeGuard([] { SDL_Quit(); });

专门的默认删除器

You can specialise std::default_deleter,通过定义以下函数对象(在本例中为具有operator()的结构):

template<>
struct std::default_delete<SDL_Window> {
  void operator()(SDL_Window *p) { SDL_DestroyWindow(p); }
};

你可以为SDL中的大多数类型执行此操作,因为它们只有一个&#34; destroy&#34;功能。 (您可能希望将其包装在宏中。)

关于这一点的好处是,从SDL 2.0.x开始,SDL_Window以及我们从SDL2/SDL.h获取的其他类型都是不完整的类型,即您无法调用{{1} }} 在他们。这意味着编译器无法开箱即用sizeof(SDL_Window)(因为它需要delete),因此您无法实例化(普通)sizeof

但是,使用专门的删除工具,std::unique_ptr<SDL_Window> 工作,并将在析构函数中调用std::unique_ptr<SDL_Window>

结果

使用上面的定义,结果是RAII的一个没有样板的例子:

SDL_DestroyWindow

答案 3 :(得分:0)

SDL_InitSDL_Quit写为RAII类:

struct SDL_exception {
  int value;
};
struct SDL {
  SDL(uint32_t arg) {
    if(int err = SDL_INIT(arg)) {
      throw SDL_exeption{err};
    }
  }
  ~SDL() {
    SDL_Quit();
  }
};

接下来,创建一个智能指针制造商:

template<class Create, class Destroy>
struct SDL_factory_t;
template<class Type, class...Args, class Destroy>
struct SDL_factory_t<Type*(*)(Args...), Destroy> {
  using Create=Type*(*)(Args...);
  Create create;
  Destroy destroy;

  using type=Type;
  using ptr = std::unique_ptr<Type, Destroy>;

  ptr operator()(Args&&...args)const {
    return {create(std::forward<Args>(args)...), destroy};
  }
};
template<class Create, class Destroy>
constexpr SDL_factory_t<Create,Destroy>
SDL_factory(Create create, Destroy destroy) {
  return {create, destroy};
};

这使得仪器可以在一个地方定义破坏,而不是在你创建物品的每个地点定义破坏。

对于您要包装的每种类型,只需执行以下操作:

constexpr auto SDL_Window_Factory = SDL_factory(SDL_CreateWindow, SDL_DestroyWindow);

现在您的代码变为:

int main()
{
  SDL init(SDL_INIT_VIDEO);
  auto win = SDL_Window_Factory("asdf", 100, 100, 640, 480, SDL_WINDOW_SHOWN);
  if (!win) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow Error: %s",
            SDL_GetError());
    return 1;
  }
}

main的身体 更少噪音,没有?

作为奖励,窗口(以及其他任何东西)在SDL退出命令之前被销毁。

我们可以在工厂中添加更多基于异常的错误处理,或者甚至以某种方式使其成为选项,.notthrowing( args... )不抛出,而( args... )执行,或者某些此类。

答案 4 :(得分:0)

如果您查看如何创建和销毁SDL库中的类型的实例,您将认识到它们通常是通过调用返回指向新创建的实例的指针的函数来创建的。通过调用使用指向要销毁的实例的指针的函数来销毁它们。例如:

利用上面的观察,我们可以定义一个类模板scoped_resource,该模板为SDL类型实现RAII惯用语。首先,我们定义以下便捷函数模板,这些模板用作SDL_CreateWindow()SDL_DestroyWindow()之类的函数的类型特征:

template<typename, typename...> struct creator_func;

template<typename TResource, typename... TArgs>
struct creator_func<TResource*(*)(TArgs...)> {
   using resource_type = TResource;
   static constexpr auto arity = sizeof...(TArgs);
};

template<typename> struct deleter_func;

template<typename TResource>
struct deleter_func<void(*)(TResource*)> {
   using resource_type = TResource;
};

然后,scoped_resource类模板:

#include <memory> // std::unique_ptr

template<auto FCreator, auto FDeleter>
class scoped_resource {
public:
   using resource_type = typename creator_func<decltype(FCreator)>::resource_type;

   template<typename... TArgs>
   scoped_resource(TArgs... args): ptr_(FCreator(args...), FDeleter) {}

   operator resource_type*() const noexcept { return ptr_.get(); }

   resource_type* get() const noexcept { return ptr_.get(); }

private:
   using deleter_pointee = typename deleter_func<decltype(FDeleter)>::resource_type;
   static_assert(std::is_same_v<resource_type, deleter_pointee>);

   std::unique_ptr<resource_type, decltype(FDeleter)> ptr_;
};

现在可以使用类型别名引入以下类型:

using Window   = scoped_resource<SDL_CreateWindow,   SDL_DestroyWindow>;
using Renderer = scoped_resource<SDL_CreateRenderer, SDL_DestroyRenderer>;

最后,您可以通过构造SDL_Window对象来创建Window实例。您将传递给Window的参数传递给SDL_CreateWindow()的构造函数:

Window win("MyWindow", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);

对象win被销毁时,将在包含的SDL_DestroyWindow()上调用SDL_Window *


但是请注意,在某些情况下,例如,在创建SDL_Surface时,您可能需要将原始创建者函数包装在自己的函数中,并将此函数作为{{1 }}。这是因为scoped_resource可能对应于扩展为函数调用而不是函数的宏。

您可能还想为每种类型创建一个自定义创建器函数,以便在创建器函数失败时能够引发异常。