C++ 使用自定义删除器创建C接口的包装器

示例

许多C接口(例如SDL2)都有自己的删除功能。这意味着您不能直接使用智能指针:

std::unique_ptr<SDL_Surface> a; // 不起作用,不安全!

相反,您需要定义自己的删除器。此处的示例使用SDL_Surface应使用该SDL_FreeSurface()函数释放的结构,但它们应适用于许多其他C接口。

删除器必须可以使用指针参数进行调用,因此可以是例如简单的函数指针:

std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);

任何其他可调用对象也将起作用,例如带有的类operator():

struct SurfaceDeleter {
    void operator()(SDL_Surface* surf) {
        SDL_FreeSurface(surf);
    }
};

std::unique_ptr<SDL_Surface, SurfaceDeleter> a(pointer, SurfaceDeleter{}); // 安全
std::unique_ptr<SDL_Surface, SurfaceDeleter> b(pointer); // 相当于以上
                                                         // 由于删除器是值初始化的

这不仅为您提供安全,零开销的unique_ptr自动内存管理(如果使用),还可以确保异常安全。

请注意,删除器是的类型的一部分,unique_ptr实现可以使用空基本优化来避免空定制删除器的大小发生任何变化。因此,尽管std::unique_ptr<SDL_Surface, SurfaceDeleter>和std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)>以相似的方式解决相同的问题,但前一种类型仍然只是指针的大小,而后一种类型必须拥有两个指针:SDL_Surface*和函数指针!当具有免费的函数自定义删除器时,最好将函数包装为空类型。

在引用计数很重要的情况下,可以使用shared_ptr而不是unique_ptr。该shared_ptr总是存储有删除,这将删除的删除器,这可能是有用的API的类型。使用shared_ptrover的缺点unique_ptr包括用于存储删除器的较高的存储成本和用于维护参考计数的性能成本。

// 构造时需要删除器,并且是该类型的一部分
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);

// 仅在构造时需要删除器,而不是该类型的一部分
std::shared_ptr<SDL_Surface> b(pointer, SDL_FreeSurface);

C ++ 17

使用template auto,我们可以更轻松地包装自定义删除器:

template <auto DeleteFn>
struct FunctionDeleter {
    template <class T>
    void operator()(T* ptr) {
        DeleteFn(ptr);
    }
};

template <class T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;

上面的示例就是这样:

unique_ptr_deleter<SDL_Surface, SDL_FreeSurface> c(pointer);

在这里,目的auto是处理所有免费功能,无论他们返回void(如SDL_FreeSurface)或没有(例如fclose)。