I would like to understand why unique_ptr
destructors require the type to be complete upon destruction while that isn't the case with shared_ptr
. This blog from Howard Hinnant briefly mentions it has to do with static vs. dynamic deleters. I'm looking for a more detailed explanation of why that might be the case (it may be compiler implementation specific in which case an example would be helpful). With dynamic deleters, does it restrict the destructor from being inlined?

- 57,834
- 11
- 73
- 112

- 1,339
- 8
- 11
-
`shared_ptr` captures a deleter at the time of construction, and stores it in the control block. E.g. `shared_ptr
ptr(new Derived);` works correctly, and eventually calls the correct destructor `~Derived`, even if `Base` doesn't have a virtual destructor (because the constructor essentially saves `[dervied_ptr]() { delete dervied_ptr; }`, and the destructor calls the same). `std::unique_ptr ` doesn't do anything like that; its destructor just does `delete p` on the raw pointer `p` of type `T*` that this `unique_ptr` stores. – Igor Tandetnik Apr 11 '22 at 00:27 -
@IgorTandetnik I've posted as a follow-up to the answer below, but what are the implications of above when it comes to using ```shared_ptr``` vs. ```unique_ptr``` when it comes to ```pImpl``` idiom. Specifically why isn't there a requirement to define the destructor of the wrapping class in the ```cpp``` file, it seems to have to do with inlining of destructor with ```unique_ptr``` that's not possible with ```shared_ptr``` but it's not obvious to me as to why. – user3882729 Apr 11 '22 at 01:11
-
1`unique_ptr
` needs `T` to be a complete type at the point where `~unique_ptr` destructor is called. `shared_ptr – Igor Tandetnik Apr 11 '22 at 02:10` needs `T` (or rather, the pointer passed to the constructor, which may e.g. be a class derived from `T`) to be complete at the point where its constructor is called. If you don't put the destructor of the wrapper class into the `cpp` file, it will be implicitly defined in the `h` file, and that implicit definition is going to call the destructor of the smart pointer, at the time `pImpl` class is incomplete. That's a problem for `unique_ptr`, not a problem for `shared_ptr`
2 Answers
Howard Hinnant was simplifying. What he precisely meant was if you use the default deleter for std::unique_ptr
, you need a complete type. For the default deleter, it simply calls delete
for you.
The gist of static and dynamic deleters is
class A;
void static_delete(A* p)
{
delete p;
}
void (*dynamic_delete)(A*) = /* somehow */;
int main()
{
A* p = /* somehow */;
static_delete(p); // undefined behaviour
dynamic_delete(p); // maybe not
}
As long asdynamic_delete
points to a function that's defined where A
is also defined, you have well-defined behaviour.
To prevent undefined behaviour, the default deleter checks for a complete type, which may be implemented as
void default_delete(A* p)
{
static_assert(sizeof(A) > 0);
delete p;
}

- 19,325
- 6
- 49
- 96
-
In the context of destructors of ```shared_ptr``` vs. ```unique_ptr``` when it applies to the ```pImpl``` idiom, the destructor of the wrapping class has to be defined in the cpp file but there is no such restriction if ```shared_ptr``` is used instead. Why might that be the case, i.e. what guarantees that the dynamic deleter will be defined after the implementation class? – user3882729 Apr 11 '22 at 01:03
-
@user3882729 If it isn't, you get a compile error when you try to create `dynamic_delete`. But that's a different issue. The point is, you don't need a complete type in `main`. – Passer By Apr 11 '22 at 01:28
I would like to understand why
unique_ptr
destructors require the type to be complete upon destruction while that isn't the case withshared_ptr
I believe that is as simple as that default destructor needs to know the size of data members. For unique_ptr
the deleter is part of the object representation, whilst for shared_ptr
the actual deleter is not that relevant, because it's stored in the control block, no matter what kind of deleter you use

- 8,416
- 2
- 19
- 49