简体   繁体   中英

Array of polymorphic unique_ptr

Using unique pointers, we can write

class A{};
class B: public A{};
class C: public A{};
std::vector<std::unique_ptr<A>> myArray;
//
f()
{
    std::unique_ptr<B> pB {new B()};
    std::unique_ptr<C> pC {new C()};
    myArray.push_back(std::move(pB));
    myArray.push_back(std::move(pC));
    // nice! now I can use the interface of A without
    // worrying about its subclasses.
}

However, if classes B and C need to have their own type-dependent custom deleter:

class A{};
class B: public A{};
class C: public A{};

template<class T>
struct custom_deleter
{ // roughly the same implementation as std::default_deleter
    void operator()(T* p)
    {
        CustomFree(p);
    }
};
//
std::vector<std::unique_ptr<A>> myArray; 
//
f()
{
    std::unique_ptr<B, custom_deleter<B>> pB {CustomAlloc(B())};
    std::unique_ptr<C, custom_deleter<C>> pC {CustomAlloc(C())};
    myArray.push_back(std::move(pB)); // error: can't convert to default deleter
    myArray.push_back(std::move(pC)); // item dito
}

Is there a way to achieve what I'm trying to do here?

Your first example has undefined behavior because A doesn't have a virtual destructor. unique_ptr 's default deleter, std::default_delete is stateless, so a unique_ptr<A> will always call delete p; , where the static type of p is A* .

Your code compiles because the default_delete<T> copy constructor can accept another default_delete<U> instance as long as U* is implicitly convertible to T* , which it is in this case. However, polymorphic deletion of the derived class object through base class pointer, when the base class destructor is not virtual is undefined behavior.


The second problem of custom deleters for each type can be solved by supplying a custom deleter that is a lambda which first casts the argument to the appropriate type before passing it on to that object's deleter.

For instance,

template<typename T>
void CustomDeleter(T *t)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    delete t;
}

std::vector<std::unique_ptr<A, void(*)(A *)>> myArray;

myArray.push_back(
    std::unique_ptr<A, void(*)(A *)>(new A(), [](A *a){ 
        CustomDeleter(a); }));
myArray.push_back(
    std::unique_ptr<A, void(*)(A *)>(new B(), [](A *a){ 
        CustomDeleter(static_cast<B*>(a)); }));
myArray.push_back(
    std::unique_ptr<A, void(*)(A *)>(new C(), [](A *a){ 
        CustomDeleter(static_cast<C*>(a)); }));

When the vector goes out of scope, this prints:

void CustomDeleter(T*) [with T = A]
void CustomDeleter(T*) [with T = B]
void CustomDeleter(T*) [with T = C]

Live demo

No, the type of the custom deleter is a template parameter to std::unique_ptr . Your easiest solution would be to add a member variable to the custom deleter:

struct custom_deleter
{
    int m_kind;
    custom_deleter(int kind)
    {
        m_kind = kind;
    }
    void operator()(A* p)
    {
        switch (m_kind)
        {
            case 0:
                CustomFree0(p);
                break;
            case 1:
                CustomFree1(p);
                break;
            //...
        }
    }
};

And then make CustomAlloc build the appropriate custom_deleter and pass it to the constructor of std::unique_ptr<A, custom_deleter> as second parameter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM