5

Why is shared_ptr<void> legal, while unique_ptr<void> is ill-formed?

 2 years ago
source link: https://stackoverflow.com/questions/39288891/why-is-shared-ptrvoid-legal-while-unique-ptrvoid-is-ill-formed
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2 Answers

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.

So, why does 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:

  • It can store objects of any type as void*, yet it is still able to delete the objects on destruction properly by correctly invoking their destructor.
  • The type of deleter is not passed as type argument to the class template, which means a little bit freedom without compromising type-safety.

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 with unique_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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK