Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a convenient way to get unique_ptr to automagically have a deleter like shared_ptr?

Tags:

c++

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)

like image 464
Cornstalks Avatar asked Oct 05 '14 04:10

Cornstalks


2 Answers

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.

like image 166
R Sahu Avatar answered Nov 15 '22 13:11

R Sahu


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.

like image 31
Cheers and hth. - Alf Avatar answered Nov 15 '22 12:11

Cheers and hth. - Alf