Is there a reason behind why std::unique_ptr
contains the deleter function signature as part of the template definition?
template<class T, class Deleter = std::default_delete<T>> class unique_ptr;
vs
template< class T > class shared_ptr;
Is there a reason behind why std::unique_ptr
contains the deleter function signature as part of the template definition?
template<class T, class Deleter = std::default_delete<T>> class unique_ptr;
vs
template< class T > class shared_ptr;
The general objective of unique_ptr
is to provide automatic deletion of a pointer when the unique-pointer goes out of scope. When the default deleter is used (just calls delete
), there is no need for any extra data member in a unique_ptr
object except for the pointer itself. This means that by default, unique_ptr
has virtually no overhead whatsoever (since most, if not all, of its functions will be inlined).
But they also wanted to be able to provide the option to change the deleter, for the special circumstances where it makes sense. The only way to provide that option, while still being able to optimize it away (storage and inlining the calls) is to make it a static part of the type itself, i.e., through a template parameter. The point is that unique_ptr
is intended to be a minimal overhead alternative for a raw pointer.
In the case of shared_ptr
, the objective is quite different and the existing overhead is too. A shared-pointer actually uses a shared storage (dynamically allocated) where it stores the pointer, the reference count and the deleter object. In other words, there is already significant overhead and an appropriate place to put the deleter object without causing extra per-pointer overhead. Furthermore, with all the reference counting mechanisms, the overhead of a virtual call (to perform the deletion) pales in comparison to the existing overhead. That's why it was a natural choice to include that convenient feature of type-erasing the deleter object in the shared-pointers.
And, if you want to create a unique-pointer type that has a type-erased deleter, it is quite simple to do with a template alias:
template <typename T>
using any_unique_ptr = std::unique_ptr< T, std::function< void(T*) > >;
Or something similar along those lines, like with this deleter:
template <typename T>
struct type_erased_delete {
std::function< void(T*) > f;
type_erased_delete() : f(std::default_delete<T>()) { };
template <typename Func>
type_erased_delete(Func&& aF) : f(std::forward<Func>(aF)) { };
void operator()(T* p) const { f(p); };
};
template <typename T>
using any_unique_ptr = std::unique_ptr< T, type_erased_delete<T> >;
It means that, if the deleter has no state, the unique_ptr
can be no larger than a plain pointer; and moving it no more expensive than copying and nulling a pointer.
To be able to handle multiple types of deleter, as shared_ptr
can, it would need to use type erasure. To do that, each unique_ptr
would need to contain a pointer to the deleter (to call it polymorphically), as well as a pointer to the managed object, doubling its size.
shared_ptr
already has that extra overhead, since it needs a pointer to access the reference count. The deleter can be stored along with the reference count.
In the case of shared_ptr
, it uses type-erasure on the deleter. There is some extra overhead in performing that type erasure, which unique_ptr
does not want. In particular, most solutions will require allocation of dynamic memory for any function object deleter. Given the goal of unique_ptr
, I can understand why they chose not to use that technique.
On the other hand, shared_ptr
already includes some extra overhead for the reference counting, as it will already require dynamic allocation for the reference count. This means that the additional overhead for the type-erasure was smaller. That, and that's how boost::shared_ptr
was written, and the standardized version was basically a capture of that.
Could be constructed with new[]
- Therefore delete[]
would need to be called - not delete
.
The template gives you this option along with things such as logging etc.