I have to write a shared pointer for class, and among many other things that it has to do is make sure it can delete the object that it is pointing to.
How can I code a solution that will work with an object that has a protected destructor?
Additionally, if the object was created using placement new, I should not be calling delete on the the object, as that space may still be in use (will the delete call even work?). How can I detect such cases?
The relevant bits of the spec:
void reset(); The smart pointer is set to point to the null pointer. The reference count for the currently pointed to object, if any, is decremented.
Sptr(); Constructs a smart pointer that points to the null pointer.
template <typename U>
Sptr(U *); Constructs a smart pointer that points to the given object. The reference count is initialized to one.
Sptr(const Sptr &);
template <typename U> Sptr(const Sptr<U> &);
The reference count is incremented. If U * is not implicitly convertible to T *, this will result in a syntax error. Note that both the normal copy constructur and a member template copy constructor must be provided for proper operation.
The way the code is called:
Sptr<Derived> sp(new Derived);
char *buf = (char *) ::operator new(sizeof(Sptr<Base1>));
Sptr<Base1> &sp2 = *(new (buf) Sptr<Base1>());
sp2 = sp;
sp2 = sp2;
sp.reset();
sp2.reset();
::operator delete(buf);
Base1 has everything protected.
The whole point of making the destructor non-public is to prevent the object from being arbitrarily destroyed. There's no good way to get around that. (Even if there is a general way, it's not a good way, as it would require breaking the hell out of encapsulation in order to do so.)
If you want an object to be destroyed by some class other than itself, make the destructor public. If you don't, then your pointer class won't be able to destroy the object either.
Alternatively, you could make the pointer class a friend of whatever classes you want it to work with. But that's ugly in a number of ways, not least of which is that it rather arbitrarily limits the valid types of objects you can use with it.
Along with the reference counter store a pointer to the function that will delete the object (a 'deleter'). You will instantiate the deleter in the templated constructor of the smart pointer, there you know the derived type. Here is an extremely naive pseudocode:
template<class T> void DefaultDeleter(void *p) { delete static_cast<T*>(p); }
struct ref_counter {
int refs;
void *p;
void (*d)(void *);
};
template<class T> class Sptr {
/* ... */
template <typename U> Sptr(U *p)
{
_c = new ref_counter;
_c->refs = 1;
_c->p = static_cast<void*>(p);
_c->d = &DefaultDeleter<U>;
_p = p;
}
T *_p;
ref_counter *_c;
};
When refs drops to zero, invoke (*_c->d)(_c->p) to destroy the pointed object.
I of course assume that the destructor of Base is protected and the one of Derived is public, as otherwise the exercise makes no sense.
Note: This is why std::shared_ptr can be safely used with base classes with a non-virtual destructor.
Your class could take a deleter functor that would then become responsible for deallocating the object. By doing this you'd dump the problem of access to the destructor onto whoever's using your class. :)
Joking aside, if the caller knows how to create instances of the class, they should also know how to destroy those instances.
This would also provide a way to solve the problems associated with placement new.
After reading your spec update I can tell you that there is no way of implementing that properly because:
There is no way for your pointer to distinguish between the new and placement new case. The only thing your deleter can do is call delete p. That is not the right thing to do in the placement new case although it might work in your example because the buffer happens to be the right size and allocated with new.
As explained in the other answers, there is no good way for your deleter to get around the protected destructor problem.
By adding the possibility of a custom deleter to the constructor you would solve both these problems.
Edit: This is how you would add a custom deleter:
template <typename U> Sptr(U *, std::function<void(T*)> &&deleter);
Constructs a smart pointer that points to the given object. The
reference count is initialized to one. The custom deleter is called
when the reference count reaches zero with the raw pointer to the
instance.
Related
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);
...
}
I am working on wrapping a C++ library into a C bridge.
All objects, I’d like to maintain with shared_ptrs on the heap like:
void* makeFoo() {
return new shared_ptr<void>(shared_ptr::make_shared<Foo>());
}
Can I use a generic destroy like this:
void destroy(void* p) {
delete static_cast<shared_ptr<void>*> p;
}
Or is there a cleaner way?
The type of the argument to delete must match the actual type of the thing you're deleting (otherwise how would the right destructor be invoked, for example?), or at least be a base type in a polymorphic hierarchy so the destructor can be found virtually ([expr.delete]/3).
So, no, you can't do that.
There are a two things at play here:
When you call new SomeType() ... then you need to call delete pointer where pointer has type SomeType * and points to the object allocated by that new expression. There are extensions to this rule with regards to base classes, but inheritance is not involved here, so we'll leave it at that.
shared_ptr<Foo> manages not only the Foo object, but also a "deleter" which knows how to destruct the Foo object. When you construct one shared_ptr from another, then that deleter gets passed on. This allows for "type erasure":
shared_ptr<Foo> typed = make_shared<Foo>();
shared_ptr<void> erased = typed;
Here, erased does no longer have compile time information about the type of the object it points to (that information was "erased"), but still has runtime information (the deleter) about the type of the object.
So to make this work, you need to make sure that you don't violate point 1 above; you need to delete the same type which you allocated with new: a shared_ptr<void>. This shared_ptr<void> needs to be constructed from a shared_ptr<Foo> because then it has a deleter which knows how to destruct a Foo:
void* makeFoo() {
shared_ptr<Foo> with_type = make_shared<Foo>();
shared_ptr<void> type_erased = with_type; // Just for illustration, merge with line below!
return new shared_ptr<void>(type_erased);
}
void destroy(void * ptr) {
shared_ptr<void> * original_ptr = ptr;
delete original_ptr;
}
makeFoo returns a pointer to a shared_ptr<void>. Just with the type info stripped, i.e. as void *.
destroy assumes it is passed such a pointer. Its delete calls the destructor of shared_ptr<void>. Because the shared_ptr<void> has the deleter of the original shared_ptr<Foo>, it knows how to actually destruct the object (Foo).
Side note: OP's code changed quite a few times, but still has basic syntax errors. This is not valid C++!!
delete <shared_ptr<void>*> p;
No. There is no such "generic delete".
Alternative solution: You can insert the std::shared_ptr<void> into a map, using the address of the dynamic object as key. The deallocation function can erase the shared pointer from the map.
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);
...
}
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);
...
}
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);
...
}