std::unique_ptr::unique_ptrThe object is empty (owns nothing), with value-initialized stored pointer and stored deleter. construct from pointer (3) The object takes ownership of p, initializing its stored pointer to p and value-initializing its stored deleter.
std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.
An explicit delete for a unique_ptr would be reset() . But do remember that unique_ptr are there so that you don't have to manage directly the memory they hold. That is, you should know that a unique_ptr will safely delete its underlying raw pointer once it goes out of scope.
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
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.
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 ).
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.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With