1

I've read an "Item" about shared_ptr in Scott Meyers' book "Effective Modern C++" where he says the following:

The usual control block implementation is more sophisticated than you might expect. It makes use of inheritance, and there’s even a virtual function. (It’s used to ensure that the pointed-to object is properly destroyed.) That means that using std::shared_ptrs also incurs the cost of the machinery for the virtual function used by the control block.

Then he doesn't explain what a virtual function does exactly. As far as I know, the proper way of deleting a pointed object is using deleters or type erasure. So, please explain what this is about.

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • Or he gave a name of "virtual function" to the "type erasure" mechanism? – Oleksii Hamov Jan 31 '15 at 16:06
  • 1
    [Type-erasing the deleter](http://stackoverflow.com/q/5450159/2756719) requires the use of virtual functions. (Well, technically it doesn't require it, but that's the most convenient way to implement it.) – T.C. Jan 31 '15 at 16:23

1 Answers1

2

The virtual function is required to ensure the object being shared is appropriately deleted. Unlike a unique_ptr, shared_ptr does not require full knowledge of the type when its template is instantiated. E.g. you can do this:

class Foo;
std::shared_ptr<Foo> foo = make_foo();

Note that in the code above we don't have the complete Foo type, just the forward declaration. If we let foo go out of scope, the object it is pointing to will be correctly deleted because when Foo was created in make_foo, a deleter was also created which knows the complete type of Foo, and hence can call the appropriate destructors. (E.g. Perhaps make_foo creates a Bar that inherits from Foo and returns that. shared_ptr's will handle this fine.)

The function on the deleter object that shared_ptr creates to manage the deletion of Foo will be virtual, allowing shared_ptr to call the correct destructor.

Roughly this could be something like this:

struct deleter_interface {
    virtual void ~deleter_interface = default;
    virtual void dispose() = 0;
};

template <typename T>
struct deleter : deleter_interface {
    T* ptr_;
    deleter(T* ptr) : ptr_(ptr) {}
    virtual void dispose() { delete ptr_; }
};

template <typename T>
shared_ptr {
    T* ptr_;
    deleter_interface* deleter_;
    ...
};

template <typename T>
shared_ptr<T>::shared_ptr<T>(T* ptr)
    : ptr_(ptr), deleter_(new deleter<T>(ptr)) {}

template <typename T>
shared_ptr<T>::~shared_ptr<T>() { deleter_->dispose(); delete deleter_; }

While this seems more complicated that is strictly necessary, it is to allow shared_ptr to be used without the complete type. For example what if you wanted to do this:

In a.h:

struct Foo;
std::shared_ptr<Foo> a = make_object();
// ... let a go out of scope

And in a.cc:

struct Foo { ... };
struct Bar : Foo { ... };
std::shared_ptr<Foo> make_object() { return std::shared_ptr<Foo>(new Bar); }

If we didn't have the virtual function used in the deleter code, then Bar would not be destructed correctly. With the virtual function it doesn't matter that the header (a.h) never sees the definition of Foo or Bar.

Shane
  • 757
  • 3
  • 11
  • hm... I think there is no way to create a `deleter_interface` object cuz it has a pure virtual function. And how `deleter_` finds out the type ? We can implement this behavour without a virtual function. – Oleksii Hamov Jan 31 '15 at 16:47
  • Yep. I rushed writing the code too quickly. I've updated so it actually works. This behaviour could be done without a virtual function, but only if you always had the complete type whenever you instantiated the shared_ptr. I'll add some extra explanation to the answer. – Shane Jan 31 '15 at 16:56
  • I mean we could have a template constructor, that receives another type's arg. e.g: `template class Shared { template Shared(U* ptr):deleter(ptr) ; //now deleter knows actual type of ptr ` But you revealed me another one way to implement this :) Thank you! P.S. What about if I want to pass my deleter? – Oleksii Hamov Jan 31 '15 at 17:25
  • You could, but you'd still need the virtual function in the deleter as the instantiation of the shared_ptr destructor will not see the original type. – Shane Jan 31 '15 at 17:37
  • Why will it not see original type? Lest consider the following instantiation `Shared ptr (new Derived());` . Here constructor receives `Derived*` hence `U` is deduced to be `Derived` and `deleter` instantiates as `deleter` . So,**shared_ptr's destructor** doesn't know the original type, but **deleter does** – Oleksii Hamov Jan 31 '15 at 17:49