6

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?

tenfour
  • 36,141
  • 15
  • 83
  • 142
  • Why would there need to be a specialization? Doesn't it already throw a compiler error? – Mooing Duck Oct 24 '11 at 19:41
  • @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 Oct 25 '11 at 13:36
  • 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 Oct 25 '11 at 15:58

4 Answers4

8

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

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
7

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!

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
  • 1
    Please note that this is UB if the pointer type passed into the constructor is a `void*` pointer. – Thomas Eding Oct 24 '11 at 19:51
  • 1
    Even if the passed-in pointer type *is* `void*`, couldn't you still avoid undefined behavior by using the two-argument constructor to pass a custom deleter that does something other than call `delete`? – Rob Kennedy Oct 24 '11 at 20:44
  • Does this answer apply only to `boost::shared_ptr` or to `std::shared_ptr` as well? I can't find a proper reference. – Mark Ransom Oct 24 '11 at 20:54
  • @Mark Ransom: as far as I know `std::shared_ptr` has the same feature. – Evan Teran Oct 24 '11 at 21:29
  • @Rob: Sure, but that would still argue for a specialization of `std::shared_ptr` which eliminated the one-argument ctor. There's a `get_deleter` but no `set_deleter`, so if you don't pass a deleter during construction it will cause undefined behavior later. – MSalters Oct 25 '11 at 08:03
  • "`shared_ptr` _is special_" No, it is not. – curiousguy Oct 25 '11 at 13:33
  • @q0987: undefined behavior – Evan Teran Dec 31 '12 at 21:54
  • @curiousguy: a year later, but i've updated the answer to reflect that this isn't something special for `shared_ptr` but works for all convertible types. – Evan Teran Dec 31 '12 at 21:58
4

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

Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
1

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.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680