98

Mr. Lidström and I had an argument :)

Mr. Lidström's claim is that a construct shared_ptr<Base> p(new Derived); doesn't require Base to have a virtual destructor:

Armen Tsirunyan: "Really? Will the shared_ptr clean up correctly? Could you please in this case demonstrate how that effect could be implemented?"

Daniel Lidström: "The shared_ptr uses its own destructor to delete the Concrete instance. This is known as RAII within the C++ community. My advice is that you learn all you can about RAII. It will make your C++ coding so much easier when you use RAII in all situations."

Armen Tsirunyan: "I know about RAII, and I also know that eventually the shared_ptr destructor may delete the stored px when pn reaches 0. But if px had static type pointer to Base and dynamic type pointer to Derived, then unless Base has a virtual destructor, this will result in undefined behavior. Correct me if I am wrong."

Daniel Lidström: "The shared_ptr knows the static type is Concrete. It knows this since I passed it in its constructor! Seems a bit like magic, but I can assure you it is by design and extremely nice."

So, judge us. How is it possible (if it is) to implement shared_ptr without requiring polymorphic classes to have virtual destructor?

starball
  • 20,030
  • 7
  • 43
  • 238
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • 3
    You could've linked to the [original thread](http://stackoverflow.com/questions/3899688/default-virtual-dtor/3899726). – Darin Dimitrov Oct 10 '10 at 09:46
  • 9
    Another interesting thing is that `shared_ptr p(new Derived)` will also destroy the `Derived` object by it's destructor, regardless if it is `virtual` or not. – dalle Oct 10 '10 at 09:57
  • 7
    Awesome way of asking a question :) – rubenvb Oct 10 '10 at 11:42
  • 5
    Even though shared_ptr allows this, it's a *really bad idea* to design a class as a base without a virtual dtor. Daniel's comments about RAII are misleading—it has nothing to do with this—but the quoted conversation sounds like a simple miscommunication (and incorrect assumption of how shared_ptr works). –  Oct 11 '10 at 18:52
  • 1
    as far as I remember, this is explained in http://channel9.msdn.com/shows/Going+Deep/C9-Lectures-Introduction-to-STL-with-Stephan-T-Lavavej/ – Philipp Apr 29 '11 at 06:01
  • 6
    Not RAII, but rather it type-erases the destructor. You have to be careful, because `shared_ptr( (T*)new U() )` where `struct U:T` won't do the right thing (and this can be done indirectly easily, such as a function that takes a `T*` and is passed a `U*`) – Yakk - Adam Nevraumont Jul 23 '14 at 00:52
  • 1
    RTFM: [This constructor is 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`.](http://www.boost.org/libs/smart_ptr/shared_ptr.htm#pointer_constructor) – Jonathan Wakely Dec 13 '14 at 02:55
  • @JonathanWakely: Great quote! As I understand, this question regards the C++ Standard Library (`std::shared_ptr`), but it draws liberally from Boost. Thus, sellibitze's answer says: `Boost does and the upcoming standard also requires this behaviour.` – kevinarpe Oct 03 '16 at 09:35
  • 4
    Wow this question was asked on 10/10/10 at (almost) 10:00. – David G Dec 12 '17 at 01:23

3 Answers3

80

Yes, it is possible to implement shared_ptr that way. Boost does and the C++11 standard also requires this behaviour. As an added flexibility shared_ptr manages more than just a reference counter. A so-called deleter is usually put into the same memory block that also contains the reference counters. But the fun part is that the type of this deleter is not part of the shared_ptr type. This is called "type erasure" and is basically the same technique used for implementing the "polymorphic functions" boost::function or std::function for hiding the actual functor's type. To make your example work, we need a templated constructor:

template<class T>
class shared_ptr
{
public:
   ...
   template<class Y>
   explicit shared_ptr(Y* p);
   ...
};

So, if you use this with your classes Base and Derived ...

class Base {};
class Derived : public Base {};

int main() {
   shared_ptr<Base> sp (new Derived);
}

... the templated constructor with Y=Derived is used to construct the shared_ptr object. The constructor has thus the chance to create the appropriate deleter object and reference counters and stores a pointer to this control block as a data member. If the reference counter reaches zero, the previously created and Derived-aware deleter will be used to dispose of the object.

The C++11 standard has the following to say about this constructor (20.7.2.2.1):

Requires: p must be convertible to T*. Y shall be a complete type. The expression delete p shall be well formed, shall have well defined behaviour and shall not throw exceptions.

Effects: Constructs a shared_ptr object that owns the pointer p.

And for the destructor (20.7.2.2.2):

Effects: If *this is empty or shares ownership with another shared_ptr instance (use_count() > 1), there are no side effects. Otherwise, if *this owns an object p and a deleter d, d(p) is called. Otherwise, if *this owns a pointer p, and delete p is called.

(emphasis using bold font is mine).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • `the upcoming standard also requires this behaviour`: (a) Which standard and (b) can you please provide a reference (to the standard)? – kevinarpe Oct 03 '16 at 09:30
  • I just want to add a comment to @sellibitze 's answer since I don't have enough points to `add a comment`. IMO, it is more `Boost does this` than `the Standard requires`. I don't think the Standard requires that from what I am understanding. Talking about @sellibitze 's example `shared_ptr sp (new Derived);`, *Requires* of `constructor` just ask for `delete Derived` being well defined and well formed. For the specification of `destructor`, there is also a `p`, but I don't think it refers to the `p` in the specification of `constructor`. – Lujun Weng May 06 '19 at 05:06
31

When shared_ptr is created it stores a deleter object inside itself. This object is called when the shared_ptr is about to free the pointed resource. Since you know how to destroy the resource at the point of construction you can use shared_ptr even with incomplete types. Whoever created the shared_ptr has already stored a correct deleter there.

For example, you can create a custom deleter:

void DeleteDerived(Derived* d) { delete d; }
shared_ptr<Base> p(new Derived, DeleteDerived);

p will call DeleteDerived to destroy the pointed object.

This is what the shared_ptr constructor does automatically, so in practice you don't need to implement that sort of a deleter unless you free by means other than a call to delete.

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
17

Simply,

shared_ptr uses special deleter function that is created by constructor that always uses the destructor of the given object and not the destructor of Base, this is a bit of work with template meta programming, but it works.

Something like that

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}
Artyom
  • 31,019
  • 21
  • 127
  • 215
  • 1
    hmm... interesting, I am starting to believe this :) – Armen Tsirunyan Oct 10 '10 at 09:50
  • 1
    @Armen Tsirunyan You should have peeked into the design description of the shared_ptr before starting the discusson. This 'capture of the deleter' is one of the essential features of shared_ptr... – Paul Michalik Oct 10 '10 at 10:16
  • 7
    @paul_71: I agree with you. On the other hand I believe this discussion was useful not only for me, but also for other people that didn't know this fact about the shared_ptr. So I guess it was not a great sin to start this thread anyway :) – Armen Tsirunyan Oct 10 '10 at 10:20
  • 4
    @Armen Of course not. Rather, you did a good job in pointing to this really very very important feature of shared_ptr which is frequently overseen even by experienced c++ developers. – Paul Michalik Oct 10 '10 at 10:42