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

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

std::unique_ptr<SDL_Surface> a; // won't work, UNSAFE!

相反,你需要定义自己的删除器。这里的示例使用 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{}); // safe
std::unique_ptr<SDL_Surface, SurfaceDeleter> b(pointer); // equivalent to the above
                                                         // as the deleter is value-initialized

这不仅为你提供安全,零开销(如果使用 unique_ptr )自动内存管理,还可以获得异常安全性。

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

在引用计数很重要的情况下,可以使用 shared_ptr 而不是 unique_ptrshared_ptr 总是存储一个删除器,这会删除删除器的类型,这可能在 API 中很有用。使用 shared_ptr 而不是 unique_ptr 的缺点包括用于存储删除器的较高存储器成本和用于维持引用计数的性能成本。

// deleter required at construction time and is part of the type
std::unique_ptr<SDL_Surface, void(*)(SDL_Surface*)> a(pointer, SDL_FreeSurface);

// deleter is only required at construction time, not part of the type
std::shared_ptr<SDL_Surface> b(pointer, SDL_FreeSurface); 

Version >= 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)。