6
votes

shared_ptr<void> is special in that it, by definiton, will invoke undefined behavior by calling delete on a void*.

So, why is there not a shared_ptr<void> specialization which throws a compile error?

4
Why would there need to be a specialization? Doesn't it already throw a compiler error? - Mooing Duck
@MooingDuck It is really impossible to explain why delete (void*)0; was allowed by the C++ committee. Or maybe it was a joke, that implementers took seriously. - curiousguy
Why do you think it's impossible, @Curiousguy? It's not as though all members of the committee are dead and left no notes. I'm sure most are still alive, and there are probably meeting minutes and correspondence. It's not ancient history. Someone willing to put forth the effort to research it could probably tell exactly why it is the way it is. - Rob Kennedy

4 Answers

8
votes

Using shared_ptr to hold an arbitrary object

shared_ptr can act as a generic object pointer similar to void*. When a shared_ptr instance constructed as:

shared_ptr<void> pv(new X);

is destroyed, it will correctly dispose of the X object by executing ~X.

This propery can be used in much the same manner as a raw void* is used to temporarily strip type information from an object pointer. A shared_ptr can later be cast back to the correct type by using static_pointer_cast.

But how?

This constructor has been changed to a template in order to remember the actual pointer type passed. The destructor will call delete with the same pointer, complete with its original type, even when T does not have a virtual destructor, or is void

7
votes

shared_ptr<T> is special in that it is by design allowed to hold a pointer to any pointer type which is convertible to T* and will use the proper deleter without UB! This comes into play with shared_ptr<Base> p(new Derived); scenarios, but also includes shared_ptr<void>.

For example:

#include <boost/shared_ptr.hpp>

struct T {
    T() { std::cout << "T()\n"; }
    ~T() { std::cout << "~T()\n"; }
};


int main() {
    boost::shared_ptr<void> sp(new T);
}

produces the output:

$ ./test
T()
~T()

If you visit http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/shared_ptr.htm, scroll down to the assignment section to see the very thing being demonstrated. See http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/sp_techniques.html#pvoid for more details.

EDIT as noted by trinithis, it is UB if the pointer type passed into the constructor is a void * pointer. Thanks for pointing that out!

4
votes

If your pointer was created via something like malloc, you can make a shared_ptr<void, dtor>, where the dtor calls free. This will result in defined behavior.

But then again, perhaps you want undefined behavior :D

1
votes

But it is valid, in many circumstances. Consider the following:

class T
{
public:
    ~T() { std::cout << "~T\n"; }
};

int main()
{
    boost::shared_ptr<void> sp(new T);
}

In circumstances where you try to pass a genuine void * into the shared_ptr constructor, you should get a compile error.