RAII dla zasobów zwracanych z C-like API

0

Cześć, piszę plugin do aplikacji z wystawionym C-like API, które zwraca wynik przez parametr. Przy ostatnim refaktorze kodu zauważyłem, że w jednym miejscu leakuje resource, żeby zapobiec takim sytuacją w przyszłości, chciałbym użyć np. std::unique_ptr do zarządzania czasem życia zwracanych zasobów. Bazując na https://dev.krzaq.cc/post/you-dont-need-a-stateful-deleter-in-your-unique_ptr-usually spłodziłem takie cuś:

    using anyID = uint64_t;
    using unique_ptr_c = std::unique_ptr<anyID[], function_caller<decltype(free_log), &free_log>>;
    
    anyID* apiResult;
    c_api(&apiResult);
    unique_ptr_c rai{apiResult};
    
    std::cout << rai[0] << "  " << rai[1];

Nie jestem pewny czy to rozwiązanie jest optymalne (czy chociażby prawidłowe) i tu pytanie do Was: jak mógłbym je usprawić/naprawić/zmienić?

PS działający kod: https://wandbox.org/permlink/6Ln63bOLyMR2F1Sh

3

W komercyjnym projekcie napisałem takie makro, które to automatyzuje:

	#define DEFINE_SMART_POINTER(SomeType, SomeTypeDeleter) \
	class C ## SomeType ## Deleter \
	{ \
	public: \
	void operator()(SomeType* pX) \
	{ \
		SomeTypeDeleter(pX); \
	}\
	}; \
	using SomeType ## _UniquePtr = std::unique_ptr<SomeType, C ## SomeType ## Deleter>

Rozmiar unique_ptr jest równy rozmiarowi gołego wskaźnika (nie trzeba trzymać deleter dla każdej instancji).
Używałem tego z powodzeniem z OpenSSL.
Zrobiłem sobie też sprytne opakowywacze, które automatycznie castowały się na SomeType** i SomeType*&, dzięki czemu można było przekazać sprytny wskaźnik bezpośrednio do wywołania funkcji, która oczekiwała tego typu.

Np biblioteka C miała taką funkcję:

bool CreateContext(Foo **p, const char name[]);

A wywoływałem to tak

DEFINE_SMART_POINTER(Foo, FreeFoo);
…
Foo_UniquePtr p;
if (CreateContext(Ref(p), "Jakas nazwa"))
{
}

Jak namierzę źródła szablonu Ref to wkleję.

3

Pisane z pamięci więc pewnie są błędy:

template<typename T>
class CRefHelper final
{
public:
    using element_type = typename T::element_type;

    ~CRefHelper()
    {
        m_smartPointer.reset(m_rawPointer);
    }
		
    CRefHelper(T& smarPointer)
        : m_smartPointer(smarPointer)
        , m_rawPointer(smarPointer.get())
    {}
		
    operator element_type** ()
    {
        return &m_rawPointer;
    }	
    operator element_type*& ()
    {
        return m_rawPointer;
    }

private:
    void* operator new (std::size_t count) = delete;

private:
    T& m_smartPointer;
    element_type* m_rawPointer;
};

template<typename T>
auto Ref(T& smartPointer) -> CRefHelper<T>
{
    return CRefHelper<T>(smartPointer);
}
1

Rozwiązanie @MarekR22 jest koszerne, ale może podam też przykład jak to zrobić bez makr - niektórzy nie lubią z zasady. Pisane z pamięci.

static inline TYPE_free(TYPE *ptr);
using TYPE_uptr = std::unique_ptr<TYPE, decltype(&TYPE_free)>;

// bazowy szablon do późniejszej specjalizacji
template<typename T>
inline auto make_unique(T*) -> std::unique_ptr<T, void(*)(T*)>;

template<>
inline TYPE_uptr make_unique(TYPE *raw_pointer)
{
    return TYPE_uptr(raw_pointer, TYPE_free);
}

Później użycie:

TYPE* c_function();

auto uptr = make_unique(c_function());

// albo jak chcemy tylko zmienną do przyszłego użycia
auto uptr2 = make_unique<TYPE>();
1

Mam lepsze rozwiązanie oparte o specjalizację szablonów.

template<class T>
class CustomDeleter
{
public:
     void operator()(T* pX)
    {
        std::default_delete<T>(pX);
    }
};

template<class T, class D = CustomDeleter<T>>
using CustomDeleterPtr = std::unique_ptr<T, D>;

template<class T, class D = CustomDeleter<T>>
auto MakeUnique(T* x) -> std::unique_ptr<T, D>
{
    return std::unique_ptr<T, D>(x);
}

#define CUSTOM_DELETER_SMART_POINTER(SomeType, SomeTypeDeleter) \
template<> \
class CustomDeleter<SomeType> \
{ \
    public: \
    void operator()(SomeType* pX) \
    { \
        SomeTypeDeleter(pX); \
    } \
}; \
 \
using SomeType ## _UniquePtr = CustomDeleterPtr<SomeType>;

Myślę, że da się jeszcze da się poprawić to i owo.

1 użytkowników online, w tym zalogowanych: 0, gości: 1