The question really fits in the title: I am curious to know what is the technical reason for this difference, but also the rationale ?
std::shared_ptr<void> sharedToVoid; // legal; std::unique_ptr<void> uniqueToVoid; // ill-formed;
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
In short, you can easily and efficiently convert a std::unique_ptr to std::shared_ptr but you cannot convert std::shared_ptr to std::unique_ptr .
You can't use shared_ptr everywhere without thinking (in contrast, you can use GC-managed references for everything), because then you'll be dumbfounded when you inevitable create cyclic references. When you program C++, you have to think about ownership. There's just no way around it.
An unique_ptr has exclusive ownership of the object it points to and will destroy the object when the pointer goes out of scope.
It is because std::shared_ptr
implements type-erasure, while std::unique_ptr
does not.
Since std::shared_ptr
implements type-erasure, it also supports another interesting property, viz. it does not need the type of the deleter as template type argument to the class template. Look at their declarations:
template<class T,class Deleter = std::default_delete<T> > class unique_ptr;
which has Deleter
as type parameter, while
template<class T> class shared_ptr;
does not have it.
shared_ptr
implement type-erasure?Well, it does so, because it has to support reference-counting, and to support this, it has to allocate memory from heap and since it has to allocate memory anyway, it goes one step further and implements type-erasure — which needs heap allocation too. So basically it is just being opportunist!
Because of type-erasure, std::shared_ptr
is able to support two things:
void*
, yet it is still able to delete the objects on destruction properly by correctly invoking their destructor.Alright. That is all about how std::shared_ptr
works.
Now the question is, can std::unique_ptr
store objects as void*
? Well, the answer is, yes — provided you pass a suitable deleter as argument. Here is one such demonstration:
int main() { auto deleter = [](void const * data ) { int const * p = static_cast<int const*>(data); std::cout << *p << " located at " << p << " is being deleted"; delete p; }; std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter); } //p will be deleted here, both p ;-)
Output (online demo):
959 located at 0x18aec20 is being deleted
You asked a very interesting question in the comment:
In my case I will need a type erasing deleter, but it seems possible as well (at the cost of some heap allocation). Basically, does this mean there is actually a niche spot for a 3rd type of smart pointer: an exclusive ownership smart pointer with type erasure.
to which @Steve Jessop suggested the following solution,
I've never actually tried this, but maybe you could achieve that by using an appropriate
std::function
as the deleter type withunique_ptr
? Supposing that actually works then you're done, exclusive ownership and a type-erased deleter.
Following this suggestion, I implemented this (though it does not make use of std::function
as it does not seem necessary):
using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>; template<typename T> auto unique_void(T * ptr) -> unique_void_ptr { return unique_void_ptr(ptr, [](void const * data) { T const * p = static_cast<T const*>(data); std::cout << "{" << *p << "} located at [" << p << "] is being deleted.\n"; delete p; }); } int main() { auto p1 = unique_void(new int(959)); auto p2 = unique_void(new double(595.5)); auto p3 = unique_void(new std::string("Hello World")); }
Output (online demo):
{Hello World} located at [0x2364c60] is being deleted. {595.5} located at [0x2364c40] is being deleted. {959} located at [0x2364c20] is being deleted.
Hope that helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With