Non-polymorphic derived destructor automatically called through shared_ptr [duplicate] - c++

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?

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).

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.

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);
...
}

Related

How does shared_ptr call the right destructor when make_shared is used to construct it?

struct Base{};
struct Derived : Base {};
std::shared_ptr<Base> sp(new Derived);
So the explanation is that sp knows that it needs to call the destructor of Derived because Derived argument was passed in the constructor. However in this case of creating a shared pointer with make_shared you don't supply the pointer, and so it can't know the Derived type. Does this mean that the correct behaviour of having a shared_ptr pointing to a Derived can only work in the case of supplying your own pointer to the constructor, not in make_shared?
shared_ptr "knows" which destructor to call because the type of the originally owned object is recorded at the point of shared_ptr construction.
In the case of constructing shared_ptr from a raw pointer:
std::shared_ptr<Base> sp(new Derived);
shared_ptr constructor internally creates an object that, among other things, stores the original pointer to Derived and an associated deleter that will be called on the pointer when the last shared_ptr referencing that object is destroyed. This internal pointer to Derived is separate from the pointer that is exposed through shared_ptr interface methods, such as get or operator->, and it stays intact for the whole lifetime of the referenced Derived object. The exposed pointer may change by constructing different shared_ptr instances, and in some cases may not even point to the Derived object at all. This is why although std::shared_ptr<Base> exposes the pointer to Base, it is still able to destroy the original Derived in the end.
The case with make_shared is similar:
std::shared_ptr<Base> sp = std::make_shared<Derived>();
Here, Derived object creation and shared_ptr initialization happens within make_shared. Simplistically, you could think of make_shared like this:
template< typename T >
std::shared_ptr<T> make_shared()
{
std::shared_ptr<T> internal_sp(new T());
return internal_sp;
}
(I'm intentionally simplifying make_shared implementation here.)
So, in make_shared, when internal_sp is initialized, it records the object type with the associated deleter just as explained above. When sp is initialized from the result of make_shared, it simply inherits the deleter and the original pointer, and converts the exposed pointer from Derived* to Base*.
This feature of shared_ptr is key to its many other useful capabilities, such as:
Hiding the deleter type from the shared_ptr type. For example, shared_ptr<FILE> spf(fopen("foo.txt", "r"), &fclose).
Flexibility in pointer conversions. For example, as noted in the comments, shared_ptr<void> spv = sp is valid, and spv will still be able to destroy Derived object as expected.
Pointer aliasing, when the exposed pointer is pointing to a different object than the one that was originally allocated. Usually, this is used when the lifetime of the pointed object is limited by the lifetime of the allocated object (e.g. the pointed object is a data member of the allocated object).
Partly, the fact that the allocated pointer and deleter are stored in a separate object, is useful for supporting weak_ptr, as it allows to store reference counters in that object as well.

Non-virtual destructor when using shared_ptr to opaque type [duplicate]

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?
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).
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.
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);
...
}

How does std::shared_ptr delete a polymorphic type with protected destructor? [duplicate]

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?
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).
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.
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);
...
}

Why shared_ptr<Base> properly destructs Derived object even if virtual destructor is not implemented [duplicate]

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?
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).
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.
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);
...
}

How is it possible (if it is) to implement shared_ptr without requiring polymorphic classes to have virtual destructor?

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?
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).
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.
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);
...
}