11

Let's say I want to use a custom deleter with an unique_ptr:

void custom_deleter(int* obj)
{
    delete obj; 
}

Why do I have to write this:

std::unique_ptr<int, void(*)(int*)> x(new int, custom_deleter);

instead of this:

std::unique_ptr<int> x(new int, custom_deleter); //does not compile

?

Can't the type of the deleter be inferred?

Barry
  • 286,269
  • 29
  • 621
  • 977
rubix_addict
  • 1,811
  • 13
  • 27
  • 1
    Related: http://stackoverflow.com/questions/21355037/why-does-unique-ptr-take-two-template-parameters-when-shared-ptr-only-takes-one – David G Apr 15 '15 at 20:44
  • 4
    Template classes don't infer template parameters. Only template functions do. – Not a real meerkat Apr 15 '15 at 20:44
  • 3
    I wonder why `std::make_unique` doesn't have an overload in which you can specify the deleter. This way, you can infer the type of the deleter. – vsoftco Apr 15 '15 at 20:48
  • @vsoftco I was looking at the docs just now and wondered the same thing... – Not a real meerkat Apr 15 '15 at 20:49
  • 2
    @vsoftco How would that work? It has to take an argument pack to construct the `T`. How would you differentiate which one of `Args...` is the `Deleter`? – Barry Apr 15 '15 at 20:49
  • @Barry: It could be done with a type tag. For example, a 'deleter' template class that contained the deleter, or a tag that always preceded the deleter in the argument list. The `make_unique` would then just have to identify that element and use it to construct the deleter where required. – Mankarse Apr 15 '15 at 20:54
  • @Barry, of course. Should have noticed this.@Mankarse, I think a type tag would make inferring the deleter impossible, turning make_unique into a function too verbose to be actually useful? (instead we should simply use unique_ptr's constructor) Correct me if I'm wrong, though. – Not a real meerkat Apr 15 '15 at 20:57

2 Answers2

6

For unique_ptr, the deleter is part of the type:

template <
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

As such, when you're constructing an object, you need to specify its type. The line you're writing:

std::unique_ptr<int> x(new int, custom_deleter);

is equivalent to:

std::unique_ptr<int, std::default_delete<int> > x(new int, custom_deleter);

And you cannot construct a std::default_delete<int> from custom_deleter.

The only way to infer the deleter type is to use template deduction on that part too:

template <typename T, typename Deleter>
std::unique_ptr<T, Deleter> make_unique_ptr(T* ptr, Deleter deleter) {
    return std::unique_ptr<T, Deleter>(ptr, deleter);
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • that's exactly what I would've done for `std::make_unique`, don't see any reason why it's not implemented like that. – vsoftco Apr 15 '15 at 20:49
  • 2
    @vsoftco [copied from above] How would that work? It has to take an argument pack to construct the T. How would you differentiate which one of Args... is the Deleter? – Barry Apr 15 '15 at 20:50
  • maybe with a `std::piecewise_construct` / `std::forward_as_tuple` – vsoftco Apr 15 '15 at 20:52
3

It cannot infer the type of the deleter, because unique_ptr by default has no state devoted to a deleter: the default deleter is stateless.

In your case, the deleter needs a pointer's worth of state, so it cannot 'fit' within the std::unique_ptr's state (which is just a pointer to a T).

This makes unique_ptr a lightweight, nearly cost-free replacement for an owning pointer.

Deductin could be done, but it would have to change the type of the resulting unique_ptr.

In comparison, shared_ptr always has state capacity for a deleter, two different atomic counters, and a pointer to value. It is heavier weight, and not a cost-free replacement for a pointer.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524