std::unique_ptr for C functions that need free
Think to a C function that return something that must be free
d, 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
?
Solution 1:
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:
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:
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:
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);
}