I found some code using std::shared_ptr to perform arbitrary cleanup at shutdown. At first I thought this code could not possibly work, but then I tried the following:
#include <memory>
#include <iostream>
#include <vector>
class test {
public:
test() {
std::cout << "Test created" << std::endl;
}
~test() {
std::cout << "Test destroyed" << std::endl;
}
};
int main() {
std::cout << "At begin of main.\ncreating std::vector<std::shared_ptr<void>>"
<< std::endl;
std::vector<std::shared_ptr<void>> v;
{
std::cout << "Creating test" << std::endl;
v.push_back( std::shared_ptr<test>( new test() ) );
std::cout << "Leaving scope" << std::endl;
}
std::cout << "Leaving main" << std::endl;
return 0;
}
This program gives the output:
At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed
I have some ideas on why this might work, that have to do with the internals of std::shared_ptrs as implemented for G++. Since these objects wrap the internal pointer together with the counter the cast from std::shared_ptr<test>
to std::shared_ptr<void>
is probably not hindering the call of the destructor. Is this assumption correct?
And of course the much more important question: Is this guaranteed to work by the standard, or might further changes to the internals of std::shared_ptr, other implementations actually break this code?
Best Answer
The trick is that
std::shared_ptr
performs type erasure. Basically, when a newshared_ptr
is created it will store internally adeleter
function (which can be given as argument to the constructor but if not present defaults to callingdelete
). When theshared_ptr
is destroyed, it calls that stored function and that will call thedeleter
.A simple sketch of the type erasure that is going on simplified with std::function, and avoiding all reference counting and other issues can be seen here:
When a
shared_ptr
is copied (or default constructed) from another the deleter is passed around, so that when you construct ashared_ptr<T>
from ashared_ptr<U>
the information on what destructor to call is also passed around in thedeleter
.