Shared void pointers. Why does this work?

11,240

Solution 1

The shared_ptr constructor that you use is actually a constructor template that looks like:

template <typename U>
shared_ptr(U* p) { }

It knows inside of the constructor what the actual type of the pointer is (X) and uses this information to create a functor that can correctly delete the pointer and ensure the correct destructor is called. This functor (called the shared_ptr's "deleter") is usually stored alongside the reference counts used to maintain shared ownership of the object.

Note that this only works if you pass a pointer of the correct type to the shared_ptr constructor. If you had instead said:

SharedVoidPointer sp1(static_cast<void*>(x));

then this would not have worked because in the constructor template, U would be void, not X. The behavior would then have been undefined, because you aren't allowed to call delete with a void pointer.

In general, you are safe if you always call new in the construction of a shared_ptr and don't separate the creation of the object (the new) from the taking of ownership of the object (the creation of the shared_ptr).

Solution 2

I think the implicit point of the question was that you can't delete by void*, so it seems strange that you can delete through shared_ptr<void>.

You can't delete an object via a raw void* primarily because it wouldn't know what destructor to call. Using a virtual destructor doesn't help because void doesn't have a vtable (and thus no virtual destructor).

James McNellis clearly explained why shared_ptr<void> works, but there is something else interesting here: Assuming you follow the documented best practice to always use the following form when invoking new...

shared_ptr<T> p(new Y);

...it is not necessary to have a virtual destructor when using shared_ptr. This is true whether T is void, or in the more familiar case where T is a polymorphic base of Y.

This goes against a long-standing conventional wisdom: That interface classes MUST have virtual destructors.

The OP's delete (void*) concern is solved by the fact that the shared_ptr constructor is a template that remembers the data type that it needs to destruct. This mechanism solves the virtual destructor problem in exactly the same way.

So even though the actual type of the object is not necessarily captured in the type of the shared_ptr itself (since T does not have to be the same type as Y), nevertheless, the shared_ptr remembers what type of object it is holding and it performs a cast to that type (or does something equivalent to that) when it comes time to delete the object.

Share:
11,240

Related videos on Youtube

Patrick
Author by

Patrick

Die-hard developer. Not afraid to go low-level (even down to assembly when needed during debugging). Prefers readable code over short, complex, 'would-be-smart', unreadable code. Musical preferences: EBM (Front 242), Synthpop (Human League, Heaven 17), Electronic stuff (Kraftwerk) and most things that don't fit mainstream (Severed Heads, Isao Tomita). Likes 80's pinballs (Comet, High Speed)

Updated on August 22, 2020

Comments

  • Patrick
    Patrick over 3 years

    To solve a very peculiar problem in my application I need a shared-pointer to allocated data, but to the outside world, the underlying data type should remain hidden.

    I could solve this by making some kind of Root class of which all my other classes inherit, and use a shared_ptr on this Root class, like this:

    std::shared_ptr<Root>
    

    However:

    • I don't want all my classes to inherit from this Root class just to be able to have this shared pointer
    • Sometimes I want to return a shared pointer to std::vector, or std::list, or std::set, ... which obviously don't inherit from my Root class

    Strange enough, it seems that you can create a shared_ptr on void and this seems to work correctly, like shown in this example:

    class X
       {
       public:
          X() {std::cout << "X::ctor" << std::endl;}
          virtual ~X() {std::cout << "X::dtor" << std::endl;}
       };
    
    typedef std::shared_ptr<void> SharedVoidPointer;
    
    int main()
    {
    X *x = new X();
    SharedVoidPointer sp1(x);
    }
    

    x is correctly deleted and in a larger experiment I could verify that the shared pointer does indeed what it needs to do (delete x afer the last shared_ptr turns out the light).

    Of course this solves my problem, since I can now return data with a SharedVoidPointer data member and be sure that it's correctly cleaned up where it should be.

    But is this guaranteed to work in all cases? It clearly works in Visual Studio 2010, but does this also work correctly on other compilers? On other platforms?

    • CB Bailey
      CB Bailey about 13 years
      possible duplicate of how boost::~shared_ptr works?
    • Patrick
      Patrick about 13 years
      @Charles, The question seems similar, but in my question I explicitly ask avoid void-pointers (not pointers to a base class), and whether this is guaranteed by the standard (not why it works in Boost).
    • CB Bailey
      CB Bailey about 13 years
      @Patrick: It's exactly the same issue. The deleter is constructed when the smart pointer takes ownership of the pointer at which time the static type information of the pointer that you pass in is used to create the deleter.
    • MSalters
      MSalters about 13 years
      Isn't there a hidden, second assumption? That question talks about a shared_ptr<Base>; this one about a shared_ptr<void>. Presumably, both work because there is an implicit conversion from Derived* to both Base* and void*. Therefore, an essential part of the answer would have to be that every (non-function) pointer type has an implicit conversion to void*. And I suspect that's not true; const void* doesn't.
    • Patrick
      Patrick about 13 years
      @MSalters, does it make sense to put a const-pointer into a shared_pointer? By storing a bare pointer in a shared_ptr, you are indicating that it is allowed to delete the instance if it is the last one pointing to it, but if the pointer is const, you are not allowed to delete it.
    • MSalters
      MSalters about 13 years
      @Patrick: Wrong. You can in fact delete both a const pointer and a pointer to a const object.
  • Patrick
    Patrick about 13 years
    Is this guaranteed to work this way by the C++0x standard? Or is this just because it is implemented this way by Microsoft?
  • James McNellis
    James McNellis about 13 years
    @Patrick: It is guaranteed in C++0x (and in C++ TR1 and in Boost).
  • RedX
    RedX about 13 years
    And you can even pass a custom close function if you are working with HANDLEs or FILEs to be called when your object is destructed.
  • Brent Bradburn
    Brent Bradburn about 13 years
    Note: The implementation in Boost actually remembers a pointer to BOTH types (T and Y). It gives you the T* for normal operation, such as T* get(), but uses the Y* when it is time to delete the object.
  • Brent Bradburn
    Brent Bradburn over 12 years
    The following answer provides additional context around the fact that shared_pointer holds two pointers which may be of different types (and even different values): stackoverflow.com/questions/6826402/…
  • Bruce
    Bruce about 10 years
    @JamesMcNellis I've posted a similar question just now, it would be great if you could help!
  • Bruce
    Bruce about 10 years
    @nobar I've posted a similar question just now, it would be great if you could help!