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 toDerived
, then unlessBase
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? Thanks in advance
A shared_ptr may share ownership of an object while storing a pointer to another object. get() returns the stored pointer, not the managed pointer.
The shared_ptr type is a smart pointer in the C++ standard library that is designed for scenarios in which more than one owner might have to manage the lifetime of the object in memory.
So no, you shouldn't. The purpose of shared_ptr is to manage an object that no one "person" has the right or responsibility to delete, because there could be others sharing ownership. So you shouldn't ever want to, either.
An object referenced by the contained raw pointer will not be destroyed until reference count is greater than zero i.e. until all copies of shared_ptr have been deleted. So, we should use shared_ptr when we want to assign one raw pointer to multiple owners. // referring to the same managed object.
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 toT*
.Y
shall be a complete type. The expressiondelete p
shall be well formed, shall have well defined behaviour and shall not throw exceptions.Effects: Constructs a
shared_ptr
object that owns the pointerp
.…
And for the destructor (20.7.2.2.2):
Effects: If
*this
is empty or shares ownership with anothershared_ptr
instance (use_count() > 1
), there are no side effects. Otherwise, if*this
owns an objectp
and a deleterd
,d(p)
is called. Otherwise, if*this
owns a pointerp
, anddelete p
is called.
(emphasis using bold font is mine).
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 with incomplete types. Whoever created the shared_ptr stored a correct deleter there.
For example, you can create a custom deleter:
void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed. shared_ptr<Base> p(new Derived, DeleteDerived);
p will call DeleteDerived to destroy the pointed object. The implementation does this automatically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With