I wanted to have a way to be able to static_cast
to the proper derived type pointer of a base class to simulate a virtual call. Here the virtual call is for a reference counting base class:
template<typename TDerived> class ReferenceCounting
which should call the destructor of the TDerived
type if the reference count drops to zero. This class achieves this by static_casting to its derived class:
delete static_cast<TDerived*>(this)
Here we expose the CRTP pattern.
Let's do a quick example how that would work:
Deriving from this class, let's say:
class A : public ReferenceCounting<A>{ ...code of A... }
and using a smart pointer (here an intrusion pointer), e.g. IntrusionPtr<A> p( new A() )
lets the object be reference counted properly.
(if you are interested, the code is below, for smart pointers I refere to boost::shared_ptr
and boost::intrusive_ptr
)
Question for multiple inheritance using CRTP:
The question came up, what to do, if let's say we want to derive a class B
from A
and would like to use it also with an intrusive pointer, of course we would like that because class A
inherits already from ReferenceCounting<A>
:
class B: public A { ...code... };
Using now an intrusive pointer naively like IntrusivePtr<B> p(new B())
will TOTALY fail. Why? Because the class ReferenceCounting
is instantiated with TDerived=A
which will then only call the destructor of A obviously.
One idea would to correct this behavior would be: We need a way to inject the proper derived type into ReferenceCounting
(here that would be TDerived=B
).
How to do this?
My answer can be found here: CRTP Multiple Inheritance.
I just give my implemented answer to the problem and try to explain it:
template<typename PossibleDerived = void>
class A : public ReferenceCounting<
typename Select< PossibleDerived,
A<PossibleDerived> >::type
>
The Select
type trait injects the correct Derived type into ReferenceCounting
, i.e A<PossibleDerived>
if PossibleDerived=void
and PossibleDerived
in any other case.
With this declaration of A
, we prepare this class to be a base of a derived B
we want to use in combination with IntrusivePtr
:
class B: public A<B> { ...code... };
If we now use a intrusive pointer like
`IntrusivePtr<B> p(new B())`
The destruction of the allocated resource B
works correctly as ReferenceCounting was instantiated with TDerived=B
(-> static_cast to B *
and deleting it).
I was interested if somebody has used this already in his code, and what improvements and pitfalls can occur by using this technique. The following pro&cons about this method I could think of so far are:
CON: Multiple static polymorphism is possible for this example but with the problem that every class in the middle of the hirarchy needs take a PossibleDerived=void
template definition. This makes also code less readable:
IntrusivePtr< A<> > p( new A<>()) // ok but hm... stupid brackets
IntrusivePtr< A<double> > q( new A<double>()) // huch, that is dangerous!, but can be avoided by no using A in this way!!
PRO: Making an Intrusive pointer from, let's say:
class C : public B{...code...}
would be dangerous as the ReferenceCounting
class has TDerived=B
and destructor call from ReferenceCounting
will not call destructor for C
!!! Fortunately this will NOT compile not compile due to a wrong cast inside IntrusionPtr
to a pointer of type ReferenceCounting<C>
(See in code below: addRef()
function). I think this property is actually awesome.
I would really appreciate to hear what you think about this code, and to hear if this kind of thing should be used or is dangerous enough to not be used and rather stick to virtual dispatching.
Code to test is here:
#include <iostream>
#include <type_traits>
using namespace std;
template<typename T> class IntrusivePtr;
template<typename TDerived>
class ReferenceCounting {
public:
typedef TDerived Derived ;
ReferenceCounting() : m_ref(0) { }
// Do not change reference count if an assignment has been done
ReferenceCounting& operator= (ReferenceCounting const&){ return *this; }
unsigned long int getRefCount() const{return m_ref;}
protected:
~ReferenceCounting() {std::cout <<"RC:DTOR" <<std::endl; }
private:
friend class IntrusivePtr<TDerived>;
friend class IntrusivePtr<const TDerived>;
unsigned long int addRef() const{
++m_ref;
std::cout << "RC::addRef: " << m_ref << std::endl;
return m_ref;
}
// NoDelete is for IntrusivePtr<T>().release()!
template<bool Delete = true>
void release() const{
--m_ref;
std::cout << "RC::release: " << m_ref << std::endl;
if(!m_ref && Delete){
std::cout << "RC::delete" << std::endl;
delete static_cast<Derived const *>(this);
}
}
mutable unsigned long int m_ref; // Mutable to be changeable also for const objects!
};
template<typename T>
class IntrusivePtr {
public:
using NonConstType = typename std::remove_const<T>::type;
IntrusivePtr() : m_p(nullptr) { }
// Explicit constructor from T* , because we want to avoid that this constructor can be used to convert implicitly to IntrusivePtr
// somewhere in the code which then deletes the resource unexpectetly!
// In this constructor/destructors we need a static_cast to really be sure if the type T inherits somehow from ReferenceCounting<T>
explicit IntrusivePtr(T* p) : m_p(p) {
if(p) static_cast<const ReferenceCounting<NonConstType> *>(m_p)->addRef();
}
IntrusivePtr(const IntrusivePtr & rhs) : m_p(rhs.m_p) {
if(m_p) static_cast<const ReferenceCounting<NonConstType> *>(m_p)->addRef();
}
// Move support (temporaries)
// Copy construct from temporary
IntrusivePtr(IntrusivePtr && rhs) : m_p( rhs.m_p ){
rhs.m_p = 0; // temporary will not invoke reference count because pointer is zero!
}
~IntrusivePtr() {
if(m_p) static_cast<const ReferenceCounting<NonConstType> *>(m_p)->release();
}
// We want to assign the intrusive ptr to this class
// m_p points to A, rhs->m_p points to B
// This means, decrease ref count of current object A, an set m_p=rhs->m_p
// and increase ref count of rhs resource. This can by:
// Copy and swap idiom, call by value to copy the IntrusivePtr (ref count of B increments)
// swap this resource pointer into the local temporary rhs (only pointer swap)
// deleting the local rhs at end of function decrements ref count of initial resource A
IntrusivePtr& operator=(IntrusivePtr rhs) {
rhs.swap(*this); // swaps the resource pointers
return *this; // delete rhs-> which decrements correctly our initial resource A!
}
// Move Assignment (from temporary)
// Make sure rhs.m_p is zero and as a consequence the destruction of rhs does not invoke release!
IntrusivePtr & operator=(IntrusivePtr && rhs){
IntrusivePtr( std::move( rhs ) ).swap(*this);
return *this;
}
// Reset the IntrusivePtr to some other resource B,
// meaning decrementing our resource A and setting the new pointer to B
// and incrementing B
// Can also take a nullptr!, making it not default argument because avoiding syntax mistakes with release()
// which does a complete different thing (takes off the IntrusivePtr)
void reset(T* p) {
// Make temporary intrusive pointer for *p (incrementing ref count B)
// swapping pointers with our resource A, and deleting temporary, decrement A
IntrusivePtr(p).swap(*this);
}
// Release a IntrusivePtr from managing the shared resource
// Decrements ref count of this resource A but without deleting it!
T* release() {
static_cast<const ReferenceCounting<NonConstType> *>(m_p)->template release<false>();
T* p = m_p;
m_p = nullptr;
return p;
}
// Get the underlying pointer
T* get() const { return m_p; }
// Implicit cast to T*
operator T*() const { return m_p; }
// Implicit cast to T&
operator T&() const { return *m_p; }
T* operator->() const { return m_p; }
void swap(IntrusivePtr& rhs) {
std::swap(m_p, rhs.m_p);
}
private:
T* m_p;
};
// Helper to select the correct Derived type, the one which gets deleted in the ReferencCounting class
template<typename PossibleDerived,typename T>
struct Select{ typedef PossibleDerived type;};
template<typename T>
struct Select<void,T>{ typedef T type; };
// The base class!
template<typename PossibleDerived = void>
class A : public ReferenceCounting< typename Select<PossibleDerived, A<PossibleDerived> >::type >{
public:
A(){}
~A(){
std::cout << "A::DTOR: " <<this << std::endl;
}
int foo(){return i[10000];}
int i[10001];
};
// Use this class (because no brackets <> to write, more handy to write code)
class ANoTemplate final : public A<ANoTemplate>{
public:
ANoTemplate(){}
~ANoTemplate(){
std::cout << "B::DTOR: " <<this << std::endl;
}
};
// if we want to derive from this class we need to be very carefull, as
template<typename PossibleDerived = void>
class B : public A< typename Select<PossibleDerived, B<PossibleDerived> >::type >{
public:
~B(){
std::cout << "B::DTOR: " <<this << std::endl;
}
};
class AddendumToC{
public:
~AddendumToC(){
std::cout << "AddendumToC::DTOR " <<this << std::endl;
}
};
// Do not derive from this class, otherwise the refernce counting base class deletes the wrong class!
class C : public B<C>, public AddendumToC{
public:
~C(){
std::cout << "C::DTOR: " <<this << std::endl;
}
};
// DOOO NOT DOO INHERIT from C if you want reference counting with an intrusive pointer!
// In fact: If we have a intrusivePtr onto a D instance, it will not compile as D cannot be cast into ReferenceCounting<D>!!)
class D : public C{};
int main(){
{
A<> *a = new A<>();
IntrusivePtr< A<> > p (a);
std::cout << "Created IntrusivePtr< A<> > " <<p<< std::endl;
}
{
ANoTemplate *a = new ANoTemplate();
IntrusivePtr< ANoTemplate > p (a);
std::cout << "Created Local IntrusivePtr< ANoTemplate > " <<p<< std::endl;
}
{
B<> *a = new B<>();
IntrusivePtr< B<> > p (a);
std::cout << "Created Local IntrusivePtr< B<> > " <<p<< std::endl;
}
{
C *a = new C();
IntrusivePtr< C > p (a);
std::cout << "Created Local IntrusivePtr< C > " <<p<< std::endl;
}
{
D *a = new D();
//IntrusivePtr< D > p (a);
//std::cout << "Created IntrusivePtr< D > " <<p<< std::endl;
//std::cout << "TAKE CARE: ONLY C gets deleted! " <<p<< std::endl;
}
{
using A=ANoTemplate;
A *a = new A();
IntrusivePtr<A> p (a);
std::cout << "Create Local " <<p<< std::endl;
{
std::cout << "Create IntrPtr" << std::endl;
IntrusivePtr<A> p2(p);
// Copy object
A c(p2);
std::cout << "Ref count of copied obj:" << c.getRefCount() << std::endl;
A b = *a;
std::cout << "Ref count of copied obj:" << b.getRefCount() << std::endl;
{
std::cout << "Create IntrPtr2 from;" << p.get()<< std::endl;
IntrusivePtr<A const> p3(p);
std::cout << "p3 points to " << p3.get() << " with ref count: " << p3->getRefCount()<< std::endl;
//p1->i[0]=10;
//p1.release(); p2.release(); // does not delete object
const A* r =p3.release();
std::cout << "Ref count of copied obj:" << r->getRefCount() << std::endl;
}
}
}
}
Select
, you could just pass bothA<PossibleDerived>
andPossibleDerived
, e.g.class A : public RefCounting < A<PossibleDerived>, PossibleDerived >
. Alternatively, add a typedef toA
to getPossibleDerived
. – dyp Jun 15 '14 at 23:35