I wrote the following code to see how a shared_ptr<void>
would behave when it is the last reference to a shared_ptr<Thing>
and is itself destroyed.
#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct Thing{
~Thing(){
cout<<"Destroyed\n";
}
int data;
};
int main(){
{
shared_ptr<void> voidPtr;
{
shared_ptr<Thing> thingPtr = make_shared<Thing>();
voidPtr = thingPtr;
}
cout<<"thingPtr is dead\n";
}
cout<<"voidPtr is dead\n";
return 0;
}
Which outputs:
thingPtr is dead
Destroyed
voidPtr is dead
It behaves in a way I like, but it's totally unexpected and I'd like to understand what's going on here. The initial shared pointer no longer exists, it's just a shared_ptr<void>
in the end. So I would expect this shared pointer to act like it's holding a void*
and have no idea about Thing::~Thing()
, yet it calls it. This is by design, right? How is the void shared pointer accomplishing this?
The shared state co-owned by shared pointers also contains a deleter, a function like object that is fed the managed object at the end of its lifetime in order to release it. We can even specify our own deleter by using the appropriate constructor. How the deleter is stored, as well as any type erasure it undergoes is an implementation detail. But suffice it to say that the shared state contains a function that knows exactly how to free the owned resource.
Now, when we create an object of a concrete type with make_shared<Thing>()
and don't provide a deleter, the shared state is set to hold some default deleter that can free a Thing
. The implementation can generate one from the template argument alone. And since its stored as part of the shared state, it doesn't depend on the type T
of any shared_pointer<T>
that may be sharing ownership of the state. It will always know how to free the Thing
.
So even when we make voidPtr
the only remaining pointer, the deleter remains unchanged, and still knows how to free a Thing
. Which is what it does when the voidPtr
goes out of scope.