std::shared_ptr
has a nifty templated constructor that automagically creates the right deleter for its given type (constructor #2 in that link).
Until just now, I (erroneously) thought std::unique_ptr
had a similar constructor, but when I ran the following code:
#include <memory>
#include <iostream>
// Notice nothing is virtual
struct Foo
{
~Foo() { std::cout << "Foo\n"; }
};
struct Bar : public Foo
{
~Bar() { std::cout << "Bar\n"; }
};
int main()
{
{
std::cout << "shared_ptr:\n";
std::shared_ptr<Foo> p(new Bar()); // prints Bar Foo
}
{
std::cout << "unique_ptr:\n";
std::unique_ptr<Foo> p(new Bar()); // prints Foo
}
}
I was surprised to learn that unique_ptr
doesn't call Bar
's destructor.
What's a clean, simple, and correct way to create a unique_ptr
that has the correct deleter for its given pointer? Especially if I want to store a whole list of these (i.e. std::vector<std::unique_ptr<Foo>>
), which means that they all must have a heterogeneous type?
(pardon the poor title; feel free to suggest a better one)
You should make the destructor of Foo
virtual
. That is good practice regardless of whether you use unique_ptr
or not. That will also take care the problem that you are dealing with.
Here's one way:
{
std::cout << "unique_ptr<Bar, void(void*)>:\n";
std::unique_ptr<Foo, void(*)(void*)> p(
new Bar(), [](void*p) -> void { delete static_cast<Bar*>( p ); }
); // prints Bar Foo
}
A main problem with this approach is that unique_ptr
supports conversion to logical "pointer to base class", but that the standard does not guarantee that conversion to void*
will then yield the same address. In practice that's only a problem if the base class is non-polymorphic while the derived class is polymorphic, introducing a vtable ptr and thus possibly changing the memory layout a bit. But in that possible-but-not-likely situation the cast back in the deleter would yield an incorrect pointer value, and bang.
So, the above is not formally safe with respect to such conversions.
To do roughly the same as a shared_ptr
does (shared_ptr
supports conversions to logical pointer-to-base), you would need to store also the original void*
pointer, along with the deleter.
In general, when you control the the topmost base class, make its destructor virtual.
That takes care of everything.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With