Shared pointers type erase destruction because they already have to handle reference counting. Going all the way to type erased destruction isn't that much more expensive.
Type erased destruction is required if you want to be able to convert shared pointers to T to shared pointers to void, which is useful sometimes. In general, it does away with the need that the data you store has a virtual destructor.
In addition, it permits an aliasing shared pointer that points within a resource owned by a shared pointer.
Your deleter obviously doesn't work; as it lacks a common base class, it can only be commonly stored in a void*
, and you cannot invoke a void*
with a (ptr)
.
Ignoring your shared::ptr
, if we look at an industrial quality std::shared_ptr
, we find that the reference control block stores the type-erased deleter at its end, and if you make_shared
it does a trick to replace the type-erased deleter with an instance of the object.
struct rc_block {
std::atomic<std::size_t> strong;
std::atomic<std::size_t> weak;
virtual void cleanup() = 0;
virtual ~rc_block() {}
};
template<class T>
struct maked_rc_block final {
std::atomic<std::size_t> strong = 1;
std::atomic<std::size_t> weak = 0;
std::aligned_storage<sizeof(T), alignof(T)> t;
template<class... Args>
maked_rc_block(Args&&...args) {
::new( (void*)&t ) T(std::forward<Args>(args)...);
}
void cleanup() override {
((T*)&t)->~T();
}
};
template<class F>
struct action_rc_block final {
std::atomic<std::size_t> strong = 1;
std::atomic<std::size_t> weak = 0;
F f;
void cleanup() { f(); }
template<class IN>
actoin_rc_block(IN&& in):f(std::forward<IN>(in)) {}
};
template<class F>
action_rc_block(F)->action_rc_block<F>;
template<class T>
struct simple_shared {
T* ptr = 0;
rc_block* counters = 0;
simple_shared( simple_shared const& o ):
ptr(o.ptr), counters(o.counters)
{ if (counters) ++(counters->strong); }
~simple_shared() {
if (counters && --(counters->strong)) {
delete counters;
}
}
template<class U>
simple_shared(U* in):
ptr(in),
counters( new action_rc_block{[in]{ delete in; }} )
{}
// explicit deleter
template<class U, class D>
simple_shared(U* in, D&& d):
ptr(in),
counters( new action_rc_block{[in,d=std::forward<D>(d)]{ d(in); }} )
{}
template<class U, class V>
simple_shared(simple_shared<U> const& alias_this, V* v):
ptr(v),
counters(alias_this.counters)
{
if(counters) ++(counters->strong);
}
template<class U>
simple_shared( maked_rc_block<U>* c ):
ptr( c?(T*)&c.t:nullptr ),
counters(c)
{}
};
template<class T, class...Args>
simple_shared<T> make_simple_shared( Args&&... args ) {
auto* counter = new make_rc_block<T>( std::forward<Args>(args)... );
return {counter};
}
My use of atomics is fast and loose, but I hope you get the idea.