How Does the Custom Deleter of Std::Unique_Ptr Work

Unique_ptr with custom deleter

You cannot use std::make_unique with a custom deleter. You’ll need to use the regular constructor instead:

auto unique = std::unique_ptr<A, Deleter>(new A(5), Deleter());

Of course this will still require you to fix the rest of your code: make the constructor of A public, and declare A before Deleter. And you’d need to replace new A(…) by the proper construction that matches the custom Deleter.

And it’s worth noting that this might leak memory if the Deleter constructor throws, so you need to make sure that it doesn’t do that.

Understanding the unique_ptr's constructor which takes a custom deleter

What does the explanation of /* see below */ actually mean?

It specifies the different behaviours of constructors for different types D, where D is the class's template argument, as in std::unique_ptr<T, D>. In particular, it considers the following three cases:

  • D is a "normal" value type, like std::unique_ptr<int, Deleter>: we can pass any object of any type A as that parameter, as long as an A can be used to copy/move construct a Deleter as appropriate.
  • D is a non-const reference type, like std::unique_ptr<int, Deleter&>: we can supply a non-const lvalue expression (and only a non-const lvalue expression), again with any type that can be used to construct a Deleter&. (This could be a derived class, for instance.) Passing rvalue expressions to this parameter is disallowed, since it doesn't make sense to store a reference to an (expired) temporary.
  • D is a const reference type, like std::unique_ptr<int, const Deleter&>: same as the above point, except const-qualified lvalue expressions are also legal.

Note that in all these cases, the type of the unique pointer is purely decided by D: the As in the parameter just allow passing values of types other than D that can be used to construct it.

How do I make use of it, as a programmer, when choosing what to pass as a deleter type template argument to std::unique_ptr?

In general, you don't need to worry about it. Specify std::unique_ptr<T, D> as appropriate for the deleter type you want to use: then, any sensible type A that can be appropriately used to construct D will work, and any that wouldn't work, won't work. The detailed specification here is increasing the implementation complexity for the purpose of reducing user complexity, after all!

Is the fact that the constructor of std::unique_ptr is templated the reason why the deleter template argument must be provided?

In essence, yes. Which way around the causation goes doesn't matter. (It could be templated to enforce "you may not use these constructors with CTAD", or it may have to be templated which results in "you may not use these constructors with CTAD": ultimately it doesn't matter.)

If the answer to the preceding quetion is affirmative, then what does the sentence The program is ill-formed if either of these two constructors is selected by class template argument deduction from the linked page mean?

That std::unique_ptr foo(value(), deleter()); is illegal, and should result in a compilation error. This is related to the way that CTAD works, see cppref's docs on CTAD to get a better idea if you're so interested.

How can _Dp and _Del actually differ, and how is this important?

We might pass an object of type A, where A is a distinct type from D, but where said object can be used to construct an object of type D. Moreover, we want to forward this type: we do not want unnecessary copies. Taking a (lvalue or rvalue, as appropriate) reference to A allows us to directly construct a D in the unique pointer. This is similar to the usage of .emplace in the standard containers.

How does the custom deleter of std::unique_ptr work?

This works for me in MSVC10

int x = 5;
auto del = [](int * p) { std::cout << "Deleting x, value is : " << *p; };
std::unique_ptr<int, decltype(del)> px(&x, del);

And on gcc 4.5, here

I'll skip going to the standard, unless you don't think that example is doing exactly what you'd expect it to do.

How do I use a custom deleter with a std::unique_ptr member?

Assuming that create and destroy are free functions (which seems to be the case from the OP's code snippet) with the following signatures:

Bar* create();
void destroy(Bar*);

You can write your class Foo like this

class Foo {

std::unique_ptr<Bar, void(*)(Bar*)> ptr_;

// ...

public:

Foo() : ptr_(create(), destroy) { /* ... */ }

// ...
};

Notice that you don't need to write any lambda or custom deleter here because destroy is already a deleter.

std::unique_ptr custom deleter that takes two arguments

The deleter is an object. It can hold onto the size.

struct LegacyDeleter
{
int size;
void operator()(char** ptr) const noexcept
{ legacyDeallocate(ptr, size); }
};
using legacy_ptr = std::unique_ptr<char*, LegacyDeleter>;

int main()
{
legacy_ptr ptr(legacyAllocatorFunction(5), LegacyDeleter{5});
}

Returning std::unique_ptr to abstract type with custom deleter from a memory pool

The code doesn't compile because std::unique_ptr<ITest> is used by ITestOwner while you to forward it std::unique_ptr<Test, std::function<void(Test*)>>. It obviously doesn't compile because std::unique_ptr<ITest> calls delete on ITest instead of calling some complex arbitrary function.

You'd need to use unique_ptr<ITest, function<void(ITest*)>> for it to work and in addition add some unsighty conversion code from function<void(Test*)> to function<void(ITest*)>... I'd say this is simply not good. unique_ptr is designed to be simple and efficient - the destructor is supposed to wrap basic functionality but it isn't convenient enough for complicated purposes.

Basically, unique_ptr is not designed for this task. It is supposed to be lightweight and you already use heavy functionality like std::function that ruins the whole purpose. Instead, you can use shared_ptr which type erases the deleter and hides it - so you'd need nothing to worry about. If you want to still restrict user to unique ownership you can surely find other 3rd party open source libraries that implement the smart pointer you want - there are lots of them.

Is a unique_ptr with custom deleter never invoked when initialized with nullptr

[...] what actually happens, is the destructor never invoked because it is UB to change the memory location of unique_ptr's managed raw ptr?

You never change the pointer managed by the std::unique_ptr to anything other than null and that's why the delete_foo is never invoked, not even with null as parameter. There's no undefined behaviour happending here, it's just that the code posted in the question doesn't behave the way you expect it to.

std::unique_ptr contains a member variable holding the pointer and the constructor you're using in the question takes the pointer by value, i.e. it copies the current value of foo resulting in later changes of the value of foo in having no effect on foo_ptr.

You could either call init_foo(&foo) before calling the constructor or provide the std::unique_ptr with ownership of the object later using std::unique_ptr::reset:

Foo* foo{ nullptr };
// Ideally you should init_foo(&foo) here before creating a uniq_ptr
std::unique_ptr<Foo, decltype(&delete_foo)> foo_ptr{ foo, &delete_foo };
init_foo(&foo); // Buggy initialization, however delete_foo will never be invoked!
foo_ptr.reset(foo);

Can I succintly declare std::unique_ptr with custom deleter?

Let the language do the hard work!

#include <memory>

struct qdr_link_t;
qdr_link_t* new_qdr_link_t();
void free_qdr_link_t(qdr_link_t*);

template <typename T, typename Deleter>
auto make_unique_ptr(T* raw, Deleter deleter)
{
return std::unique_ptr<T, Deleter>(raw, deleter);
}

//std::unique_ptr<qdr_link_t, decltype(&free_qdr_link_t)> link{new_qdr_link_t(), free_qdr_link_t};
auto link = make_unique_ptr(new_qdr_link_t(), free_qdr_link_t);

Add std::forward to taste (if you care).


For C++11, you'll need to add the trailing return type -> std::unique_ptr<T, Deleter> to make_unique_ptr, or just put that in the "normal" return type.



Related Topics



Leave a reply



Submit