Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why unique_ptr doesn't prevent slicing of custom deleter?

The behavior of std::unique_ptr with custom deleter is based on the static type of the deleter. No polymorphism, no runtime behavior based on actual deleter passed in runtime, as derived deleter provided is being sliced to the static type of the declared deleter.

(It is designed this way in purpose, to allow the size of unique_ptr with default deleter or with custom deleter without any data members, to have same size as a raw pointer).

static behavior of unique_ptr with custom deleter:

class A {};

struct BaseDeleter {
    virtual void operator()(A* p) const {
        std::cout << "in BaseDeleter" << std::endl; 
        delete p;
    }
};

struct DerivedDeleter: BaseDeleter {
    void operator()(A* p) const override {
        std::cout << "in DerivedDeleter" << std::endl; 
        delete p;
    }
};

int main() {
    auto unique_var = std::unique_ptr<A, BaseDeleter>(new A);
    unique_var = std::unique_ptr<A, DerivedDeleter>(new A);
}

Output:

in BaseDeleter
in BaseDeleter

This is opposed to std::shared_ptr who holds its custom deleter differently and allowing dynamic behavior:

dynamic behavior of shared_ptr with custom deleter:

int main() {
    auto shared_var = std::shared_ptr<A>(new A, BaseDeleter{});
    shared_var = std::shared_ptr<A>(new A, DerivedDeleter{});
}

Output:

in BaseDeleter
in DerivedDeleter

Code: https://coliru.stacked-crooked.com/a/54a8d2fc3c95d4c1


The behavior of assigning std::unique_ptr with different custom deleter is actually slicing.

Why unique_ptr doesn't prevent slicing of custom deleter?

Why didn't the language block the assignment of std::unique_ptr if the assigned unique_ptr has different custom deleter, to avoid slicing?


This seems to be possible as presented below.

Blocking unique_ptr from slicing of custom deleter

template<typename TYPE, typename Deleter>
struct my_unique_ptr : std::unique_ptr<TYPE, Deleter> {
    using BASE = std::unique_ptr<TYPE, Deleter>;
    using std::unique_ptr<TYPE, Deleter>::unique_ptr;
    auto& operator=(std::nullptr_t) noexcept {
        return BASE::operator=(nullptr);
    }
    template<typename T, typename OtherDeleter,
      std::enable_if_t<!std::is_same<OtherDeleter, Deleter>::value>* dummy = nullptr>
    auto& operator=(std::unique_ptr<T, OtherDeleter>&& other) = delete;
};

Code: http://coliru.stacked-crooked.com/a/089cd4c7303ad63e

like image 471
Amir Kirsh Avatar asked May 25 '19 20:05

Amir Kirsh


People also ask

Why should you use unique_ptr?

std::unique_ptr represents data which has only one owner at any given time. It should be your default choice when you need a smart pointer. You can move a std::unique_ptr around to keep it alive, but there can only be one owner of the data. After moving the pointer, the previous pointer object is invalidated.

Does unique_ptr delete automatically?

That is, you should know that a unique_ptr will safely delete its underlying raw pointer once it goes out of scope.

What happens when you move a unique_ptr?

A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it. We recommend that you restrict an object to one owner, because multiple ownership adds complexity to the program logic.

What is the scope of unique_ptr?

An​ unique_ptr has exclusive ownership of the object it points to and ​will destroy the object when the pointer goes out of scope. A unique_ptr explicitly prevents copying of its contained pointer.


1 Answers

struct B {
  virtual ~B() = default;
};

struct D : B {};

std::unique_ptr<B> b;
b = std::make_unique<D>();

Here we have a classic use case. Yes, the deleter is sliced, but deletion is still well defined. Your proposal will interfere with that. And will probably be very hard to amend reliably into not interfering.

One can always specify a custom deleter like std::function<void(void*)> to get polymorphism by type erasure. Sure, it has overhead, but that's opt in.

By default unique_ptr is optimized for the more common use cases, with less common ones that require overhead being possible by request.

like image 90
StoryTeller - Unslander Monica Avatar answered Sep 22 '22 00:09

StoryTeller - Unslander Monica