使用自定義刪除器建立 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)。