std::unique_ptr for C functions that need free

C++C++11

C++ Problem Overview


Think to a C function that return something that must be freed, for example the POSIX's strdup(). I want to use that function in C++11 and avoid any chance of leaks, is this a correct way?

#include <memory>
#include <iostream>
#include <string.h>

int main() {
    char const* t { "Hi stackoverflow!" };
    std::unique_ptr<char, void(*)(void*)>
        t_copy { strdup(t), std::free };

    std::cout << t_copy.get() << " <- this is the copy!" <<std::endl;
}

Assuming it makes sense, it is possible to use a similar pattern with non-pointers? For example for the POSIX's function open that returns an int?

C++ Solutions


Solution 1 - C++

What you have is extremely likely to work in practice, but not strictly correct. You can make it even more likely to work as follows:

std::unique_ptr<char, decltype(std::free) *>
    t_copy { strdup(t), std::free };

The reason is that the function type of std::free is not guaranteed to be void(void*). It is guaranteed to be callable when passed a void*, and in that case to return void, but there are at least two function types that match that specification: one with C linkage, and one with C++ linkage. Most compilers pay no attention to that, but for correctness, you should avoid making assumptions about it.

However, even then, this is not strictly correct. As pointed out by @PeterSom in the comments, C++ allows implementations to e.g. make std::free an overloaded function, in which case both your and my use of std::free would be ambiguous. This is not a specific permission granted for std::free, it's a permission granted for pretty much any standard library function. To avoid this problem, a custom function or functor (as in his answer) is required.

> Assuming it makes sense, it is possible to use a similar pattern with non-pointers?

Not with unique_ptr, which is really specific to pointers. But you could create your own class, similar to unique_ptr, but without making assumptions about the object being wrapped.

Solution 2 - C++

The original question (and hvd's answer) introduce a per-pointer overhead, so such a unique_ptr is twice the size than one derived with std::make_unique. In addition, I would formulate the decltype directly:

std::unique_ptr<char, decltype(&std::free)>
    t_copy { strdup(t), &std::free };

If one has many of those C-API-derived pointers that extra space can be a burden hindering adoption of safe C++ RAII for C++ code wrapping existing POSIX style APIs requiring to be free()d. Another problem that can arise, is when you use char const in the above situation, you get a compile error, because you can not automatically convert a char const * to the Parameter type of free(void *).

I suggest to use a dedicated deleter type, not one built on the fly, so that the space overhead goes away and the required const_cast is also not a problem. A template alias then can easily be used to wrap C-API-derived pointers:

struct free_deleter{
	template <typename T>
	void operator()(T *p) const {
		std::free(const_cast<std::remove_const_t<T>*>(p));
	}
};
template <typename T>
using unique_C_ptr=std::unique_ptr<T,free_deleter>;
static_assert(sizeof(char *)==
			  sizeof(unique_C_ptr<char>),""); // ensure no overhead

The example now becomes

unique_C_ptr<char const> t_copy { strdup(t) };

I will suggest that that mechanism should be made available in the C++ Core Guidelines support library ( maybe with a better naming ), so it can be made available for code bases transitioning to modern C++ ( success open ).

Solution 3 - C++

> Assuming it makes sense, it is possible to use a similar pattern with > non-pointers? For example for the POSIX's function open that returns > an int?

Yes, it can be done. You need a "pointer" type that satisfies the NullablePointer requirements:

struct handle_wrapper {

    handle_wrapper() noexcept : handle(-1) {}
    explicit handle_wrapper(int h) noexcept : handle(h) {}
    handle_wrapper(std::nullptr_t)  noexcept : handle_wrapper() {}

    int operator *() const noexcept { return handle; }
    explicit operator bool() const noexcept { return *this != nullptr; }

    friend bool operator!=(const handle_wrapper& a, const handle_wrapper& b) noexcept {
        return a.handle != b.handle;
    }

    friend bool operator==(const handle_wrapper& a, const handle_wrapper& b) noexcept {
        return a.handle == b.handle;
    }

    int handle;
};

Note that we use -1 as the null handle value here because that's what open() returns on failure. It's also very easy to templatize this code so that it accepts other handle types and/or invalid values.

Then

struct posix_close
{
    using pointer = handle_wrapper;
    void operator()(pointer fd) const
    {
        close(*fd);
    }
};

int
main()
{
    std::unique_ptr<int, posix_close> p(handle_wrapper(open("testing", O_CREAT)));
    int fd = *p.get();
}

Demo.

Solution 4 - C++

> Assuming it makes sense, it is possible to use a similar pattern with > non-pointers? For example for the POSIX's function open that returns > an int?

Sure, using Howard's Hinnant tutorial on unique_ptr, we can see a motivating example:

// For open
#include <sys/stat.h>
#include <fcntl.h>

// For close
#include <unistd.h>

// For unique_ptr
#include <memory>

int main()
{
    auto handle_deleter = [] (int* handle) {
        close(*handle);
    };
    
    int handle = open("main.cpp", O_RDONLY);
    std::unique_ptr<int, decltype(handle_deleter)> uptr
        { &handle, handle_deleter };
}

Alternatively you can use a functor instead of a lambda:

struct close_handler
{
    void operator()(int* handle) const
    {
        close(*handle);
    }
};

int main()
{
    int handle = open("main.cpp", O_RDONLY);
    std::unique_ptr<int, close_handler> uptr
        { &handle };
}

The example can be further reduced if we use a typedef and a "factory" function.

using handle = int;
using handle_ptr = std::unique_ptr<handle, close_handler>;

template <typename... T>
handle_ptr get_file_handle(T&&... args)
{
    return handle_ptr(new handle{open(std::forward<T>(args)...)});
}

int main()
{
    handle_ptr hp = get_file_handle("main.cpp", O_RDONLY);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPaolo.BolzoniView Question on Stackoverflow
Solution 1 - C++user743382View Answer on Stackoverflow
Solution 2 - C++PeterSomView Answer on Stackoverflow
Solution 3 - C++T.C.View Answer on Stackoverflow
Solution 4 - C++user3920237View Answer on Stackoverflow