Question

In the recent overload journal under the topic Enforcing the rule of zero, the authors describe how we can avoid writing the Rule of five operators as the reasons for writing them are:

  1. Resource management
  2. Polymorphic deletion

And both these can be taken care of by using smart pointers.

Here I am specifically interested in the second part.

Consider the following code snippet:

class Base
{
public:
    virtual void Fun() = 0;
};


class Derived : public Base
{
public:

    ~Derived()
    {
        cout << "Derived::~Derived\n";
    }

    void Fun()
    {
        cout << "Derived::Fun\n";
    }
};


int main()
{
    shared_ptr<Base> pB = make_shared<Derived>();
    pB->Fun();
}

In this case, as the authors of the article explain, we get polymorphic deletion by using a shared pointer, and this does work.

But if I replace the shared_ptr with a unique_ptr, I am no longer able to observe the polymorphic deletion.

Now my question is, why are these two behaviors different? Why does shared_ptr take care of polymorphic deletion while unique_ptr doesn't?

Was it helpful?

Solution

You have your answer here: https://stackoverflow.com/a/22861890/2007142

Quote:

Once the last referring shared_ptr goes out of scope or is reset, ~Derived() will be called and the memory released. Therefore, you don't need to make ~Base() virtual. unique_ptr<Base> and make_unique<Derived> do not provide this feature, because they don't provide the mechanics of shared_ptr with respect to the deleter, because unique pointer is much simpler and aims for the lowest overhead and thus is not storing the extra function pointer needed for the deleter.

OTHER TIPS

It'll work if you use the C++14 make_unique or write your own one like in Yakk's answer. Basically the difference between the shared pointer behavior is that you got:

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

for unique_pointer and as you can see, the deleter belongs to the type. If you declare a unique_pointer<Base> it'll always use std::default_delete<Base> as default. But make_unique will take care of using the correct deleter for your class.

When using shared_ptr you got:

template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

and other overloads as constructor. As you can see the default deleter for unique_ptr depends on the template parameter when declaring the type (unless you use make_unique) whilst for shared_ptr the deleter depends on the type passed to the constructor.


You can see a version that allows polymorphic delete without virtual destructor here (this version should also work in VS2012). Note that it is quite a bit hacked together and I'm currently not sure what the behavior of unique_ptr and make_shared in C++14 will be like, but I hope they'll make this easier. Maybe I'll look into the papers for the C++14 additions and see if something changed if I got the time later.

template<typename T>
using smart_unique_ptr=std::unique_ptr<T,void(*)(void*)>;

template<class T, class...Args> smart_unique_ptr<T> make_smart_unique(Args&&...args) {
  return {new T(std::forward<Args>(args)...), [](void*t){delete (T*)t;}};
}

The problem is that the default deleter for unique_ptr calls delete on the stored pointer. The above stores a deleter that knows the type at construction, so when copied to base class unique_ptr will still delete as the child.

This adds modest overhead, as we have to dereference a pointer. In addition it denormalizes the type, as default constructed smart_unique_ptrs are now illegal. You can fix this with some extra work (replace a raw function pointer with a semi smart functor that at least does not crash: the function pointer, however, should be asserted to exist if the unique is non-empty when the deleter is invoked).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top