Typedef带有静态自定义删除器的shared_ptr类型,类似于unique_ptr

时间:2014-12-06 11:44:32

标签: c++ shared-ptr smart-pointers unique-ptr raii

我已经阅读了很多关于shared_ptrunique_ptr的自定义删除操作的问题,以及两者之间的区别。但是,我仍然没有找到这个问题的任何明确答案:

如何最好地创建一个充当shared_ptr自定义删除器的类型,类似于unique_ptr如何将删除器作为类型定义的一部分?

对于unique_ptr用法,我使用一个删除类来处理各个类型的删除(为简洁起见,将其限制为两种类型):

struct SDL_Deleter {                                
  void operator()( SDL_Surface* ptr ) { if (ptr) SDL_FreeSurface( ptr );} 
  void operator()( SDL_RWops* ptr )   { if (ptr) SDL_RWclose( ptr );} 
};

using SurfacePtr = std::unique_ptr<SDL_Surface, SDL_Deleter>;
using RWopsPtr = std::unique_ptr<SDL_RWops, SDL_Deleter>;

可以使用类似

的内容
SurfacePtr surface(IMG_Load("image.png"));

在销毁时会致电SDL_FreeSurface


这一切都很好。但是,如何为shared_ptr实现同样的目标呢?其类型定义为

template< class T > class shared_ptr;

并且提供自定义删除器的方法是通过构造函数。 shared_ptr包装器的用户需要知道包装了哪个指针类型以及应该如何删除该指针,这感觉不对。与上述unique_ptr示例一样,实现相同用法的最佳方法是什么。

换句话说,我最终会得到:

SurfaceShPtr surface(IMG_Load("image.png"));

而不是

之类的东西
SurfaceShPtr surface(IMG_Load("image.png"),
                     [=](SDL_Surface* ptr){SDL_FreeSurface(ptr);});

或者,稍微好一些

SurfaceShPtr surface(IMG_Load("image.png"),
                     SDL_Deleter());

有没有办法做到这一点,无需创建RAII包装类(而不是typedef),增加了更多的开销?


如果答案是&#34;这是不可能的&#34;。为什么不呢?

2 个答案:

答案 0 :(得分:5)

这里提供的另一个答案是,通过自定义删除器unique_ptr的函数返回可以完成我所要求的内容,可以隐式转换为shared_ptr

给出的答案是std::shared_ptr无法将删除定义为类型特征。答案建议作为替代方案,使用返回unique_ptr的函数,隐式转换为shared_ptr

由于这不是该类型的一部分,因此可能会犯一个简单的错误,导致内存泄漏。这就是我想要避免的。

例如:

// Correct usage:
shared_ptr<SDL_Surface> s(createSurface(IMG_Load("image.png")));

// Memory Leak:
shared_ptr<SDL_Surface> s(IMG_Load("image.png"));

我想表达的概念是将删除器作为类型的一部分(unique_ptr允许),但具有shared_ptr的功能。我建议的解决方案是从shared_ptr派生,并提供删除器类型作为模板参数。这不占用额外的内存,其工作方式与unique_ptr相同。

template<class T, class D = std::default_delete<T>>
struct shared_ptr_with_deleter : public std::shared_ptr<T>
{
  explicit shared_ptr_with_deleter(T* t = nullptr)
      : std::shared_ptr<T>(t, D()) {}

  // Reset function, as it also needs to properly set the deleter.
  void reset(T* t = nullptr) { std::shared_ptr<T>::reset(t, D());  }
};

与删除者类一起(感谢Jonathan Wakely。比我的宏更清洁(现已删除)):

struct SDL_Deleter
{
  void operator()(SDL_Surface* p) const { if (p) SDL_FreeSurface(p); }
  void operator()(SDL_RWops* p) const { if (p) SDL_RWclose(p); }
};

using SurfacePtr = std::unique_ptr<SDL_Surface, SDL_Deleter>;
using SurfaceShPtr = shared_ptr_with_deleter<SDL_Surface, SDL_Deleter>;

using RWopsPtr = std::unique_ptr<SDL_RWops, SDL_Deleter>;
using RWopsShPtr = shared_ptr_with_deleter<SDL_RWops, SDL_Deleter>;

SurfaceShPtr成员的实例类型保证正确清理,与SurfacePtr相同,这就是我想要的。

// Correct Usage (much harder to use incorrectly now):
SurfaceShPtr s(IMG_Load("image.png"));

// Still correct usage
s.reset(IMG_Load("other.png"));

我会暂时搁置一段时间,发表评论等,而不接受答案。也许有更多危险的警告我错过了(非虚拟析构函数不是一个,因为父shared_ptr负责删除)。

答案 1 :(得分:2)

typedef是一个静态的编译时功能。

传递给shared_ptr的删除器是动态的运行时属性。删除者是&#34;类型删除&#34;并且不是shared_ptr界面的一部分。

因此,您无法声明typedef来表示替代删除器,只需将其传递给构造函数。

  

与上述unique_ptr示例一样,实现相同用法的最佳方法是什么。

您可以使用函数创建资源并在shared_ptr

中返回它们
shared_ptr<SDL_Surface> create_sdl_surface(const char* s)
{
  return shared_ptr<SDL_Surface>(IMG_load(s), SDL_FreeSurface);
}

但我会让这些函数返回unique_ptr,可以转换为shared_ptr,如下所示。

我会摆脱宏并做这样的事情:

// type with overloaded functions for freeing each resource type
struct SDL_deleter
{
  void operator()(SDL_Surface* p) const { if (p) SDL_FreeSurface(p); }
  void operator()(SDL_RWops* p) const { if (p) SDL_RWclose(p); }
  // etc.
};

// a unique_ptr using SDL_deleter:
template<typename P>
  using SDL_Ptr = std::unique_ptr<P, SDL_deleter>;

// typedefs for the common ptr types:
using SurfacePtr = SDL_ptr<SDL_Surface>;
using RWopsPtr = SDL_ptr<SDL_RWops>;
// etc.

要回答问题的shared_ptr部分,请定义创建资源的函数并将其返回到SDL_ptr

SurfacePtr createSurface(const char* s) { return SurfacePtr(IMG_load(s)); }
RWopsPtr createRWops([...]) { return RWopsPtr([...]); }
// etc.

然后,您可以从这些函数的结果中轻松创建shared_ptr

shared_ptr<SDL_Surface> s = createSurface("image.png");

shared_ptr会自动从unique_ptr获取正确的删除者。