Sign in to follow this  
ZuBsPacE

Template RefCountPointer Cast/Polymorphism Problem

Recommended Posts

Hi there, I'm playing around with a custom Reference Counted Pointer class and ran into a problem.
RefCountPointer< T > 
           |
           v
    RefCountData< T >
           ^
           |
RefCountPointer< T >

Each RefCountPointer instance holds a pointer to the same RefCountData instance, which stores a count variable and a raw pointer to the encapsulated object. So far so good, but I want to extend this to allow polymorphism like this:
RefCountPointer< Derived > pDerived( new Derived() );
RefCountPointer< Base > pBase( pDerived );

Now within "RefCountPointer< T >::RefCountPointer( RefCountPointer< U > pIn )" i need to somehow cast the RefCountData< U > to a RefCountData< T > to work properly, but I'm running out of ideas right now.. Would be nice if someone of you could push me into the right direction.. Here's a short example illustrating my current attempt which doesn't work (as far as I can tell reinterpret_cast messes around with the vtable):
struct Base1
{
	virtual ~Base1() {};

	virtual void Test1() = 0;
};


struct Base2
{
	virtual ~Base2() {};

	virtual void Test2() = 0;
};


struct Derived : public Base1, public Base2
{
	Derived() : Base1(), Base2() {};

	virtual ~Derived() {};

	virtual void Test1() {};
	virtual void Test2() {};
};



template< typename T >
struct Container
{
	T*				m_Ptr;
};



int main( int, char** )
{
	Derived* pDerived = new Derived();
	
	// Following Method Calls Work
	pDerived->Test1();	
	pDerived->Test2();


	// UpCast Of pDerived To A Base1 Pointer
	Base1* pBase1 = pDerived;

	// Following Method Call Works
	pBase1->Test1();


	// UpCast Of pDerived To A Base1 Pointer
	Base2* pBase2 = pDerived;

	// Following Method Call Works
	pBase2->Test2();


	// Up Until Now Everything Works As Expected, Now I'm Trying To Do
	// The Same, But Instead Of Using Raw Pointers, I Encapsulate Them
	// In The Template-Container.


	// Encapsulating pDerived In A Container
	Container< Derived >* pContainerDerived = new Container< Derived >();
	pContainerDerived->m_Ptr = pDerived;

	// Following Method Calls Work
	pContainerDerived->m_Ptr->Test1();	
	pContainerDerived->m_Ptr->Test2();	


	// Brute Force Cast Of The Derived Container To A Base1 Container.. Is There Another Way?
	Container< Base1 >* pContainerBase1 = reinterpret_cast< Container< Base1 >* >( pContainerDerived );

	// Following Method Call Works
	pContainerBase1->m_Ptr->Test1();


	// Brute Force Cast Of The Derived Container To A Base2 Container .. Is There Another Way?
	Container< Base2 >* pContainerBase2 = reinterpret_cast< Container< Base2 >* >( pContainerDerived );

	// ERROR! Following Method Calls Test1()... =[
	pContainerBase2->m_Ptr->Test2();
	

	// Clean Up The Mess;
	delete pContainerDerived;
	delete pDerived;

	return 0;
}


Share this post


Link to post
Share on other sites
Why is RefCountData< T > templated? It's an internal data structure for which you can ensure type safety in RefCountPointer.

It's just a std::pair<void *, unsigned int> type of tuple. It doesn't care about types, only about the count.

Share this post


Link to post
Share on other sites
Hi, are you sure it calls Test1 instead of Test2 ? I'm not sure, but maybe the compiler makes no difference between 2 empty methods, both void and no parameters. Have you tried with some code in those methods ?
About the cast, I can't find any problem with it. But I think a template constructor for RefCountData could do it :

template<class T>;
class RefCountData
{
...
template<class U>; RefCountData(RefCountData<U>& other){ // the cast comes here }
...
}


so you should be able to do this :
Container<Base2>* pContainerBase2 = pContainerDerived;

Maybe I'm all wrong...

Share this post


Link to post
Share on other sites
Antheus' suggestion interacts badly with multiple inheritance. However, there's a lot of truth in what he says: give your class a pointer to the stored object and a pointer to a shared reference count integer. This is equivalent to adding a a global reference field with another level of indirection to your RefCountData object.

Share this post


Link to post
Share on other sites
Add a conversion operator for implicit casts.

template< typename T >
struct Container
{
T* m_Ptr;
template<typename T1>Container(Contaner<T1>& rhs):m_Ptr(rhs.m_Ptr){}
};

Container< Base2 > pContainerBase2(pContainerDerived);


I dont really understand why you are using pointers to Container classes, you also want to look at overloading operators such as "=" "->" "*" "bool" etc.

Share this post


Link to post
Share on other sites
Hi again, thx for all your replies.


Quote:
Original post by ToohrVyk
Antheus' suggestion interacts badly with multiple inheritance. However, there's a lot of truth in what he says: give your class a pointer to the stored object and a pointer to a shared reference count integer. This is equivalent to adding a a global reference field with another level of indirection to your RefCountData object.

If I understood you correctly, you suggest adding T* as a member to each RefCountPtr<T> instance. This way it's possible to un-template RefCountData which is shared.

A very simple solution. I got it working below. I still need a reinterpret_cast, but I guess it's safe if there's no vtable involved.

Right now this will get me going. I think i'll have to look into boost::shared_ptr later on to find out how they did it; adding a ptr-member to each RefCountPtr instance seems like redundant data.



First the Non-Working example:

#include <iostream>


template< typename T >
class RefCountPtr
{

template< typename T >
class RefCountData; // Private Inner Class Forward Declaration

template< typename U >
friend class RefCountPtr; // Enable Access To RefCountData* Of Other RefCountPtr Types


public:

RefCountPtr( T* ptr )
: m_pRefCountData( new RefCountData< T >( ptr ) )
{
m_pRefCountData->AddCount();
}


RefCountPtr( const RefCountPtr< T >& in )
: m_pRefCountData( in.m_pRefCountData )
{
m_pRefCountData->AddCount();
}


template< typename U >
RefCountPtr( const RefCountPtr< U >& in )
{
// Getting Rid Of The const...
RefCountPtr< U > constHack( in );

// Evil Cast
m_pRefCountData = reinterpret_cast< RefCountData< T >* >( constHack.m_pRefCountData );

m_pRefCountData->AddCount();
}



~RefCountPtr()
{
m_pRefCountData->RemoveCount();
}


T* operator->() const
{
return m_pRefCountData->GetPtr();
}


T& operator*() const
{
return *( m_pRefCountData->GetPtr() );
}


private:

RefCountData< T >* m_pRefCountData;


private:

template< typename T >
class RefCountData
{
public:

RefCountData( T* ptr )
: m_Ptr( ptr ), m_nCount( 0 )
{
}

void AddCount()
{
++m_nCount;
};

void RemoveCount()
{
if( --m_nCount == 0 )
{
delete m_Ptr;
delete this;

// "delete this" Works If The DTor Is Empty
// And If "this" Won't Be Accessed Afterwards
}
};

T* GetPtr()
{
return m_Ptr;
};


private:

T* m_Ptr;
unsigned int m_nCount;
};

};


struct Base1
{
virtual ~Base1() {};

virtual void Test1() = 0;
};


struct Base2
{
virtual ~Base2() {};

virtual void Test2() = 0;
};


struct Derived : public Base1, public Base2
{
Derived() : Base1(), Base2() {};

virtual ~Derived() {};

virtual void Test1() { std::cout << "Test1\n";};
virtual void Test2() { std::cout << "Test2\n";};
};



int main( int, char** )
{
// Ok
RefCountPtr< Derived > pDerived( new Derived() );
pDerived->Test1();
pDerived->Test2();


// Ok
RefCountPtr< Base1 > pBase1( pDerived );
pBase1->Test1();


// Error, Calls Test1() !!
RefCountPtr< Base2 > pBase2( pDerived );
pBase2->Test2();


// Output:
//
// Test1
// Test2
// Test1
// Test1


return 0;
}






Now the Working example:
- RefCountData now only holds the counter, no template needed.
- each RefCountPointer holds a copy of the pointer to the encapsulated object

#include <iostream>


template< typename T >
class RefCountPtr
{

class RefCountData; // Private Inner Class Forward Declaration

template< typename U >
friend class RefCountPtr; // Enable Access To RefCountData* Of Other RefCountPtr Types


public:

RefCountPtr( T* ptr )
: m_pRefCountData( new RefCountData() ), m_Ptr( ptr )
{
m_pRefCountData->AddCount();
}


RefCountPtr( const RefCountPtr< T >& in )
: m_pRefCountData( in.m_pRefCountData ), m_Ptr( in.m_Ptr )
{
m_pRefCountData->AddCount();
}


template< typename U >
RefCountPtr( const RefCountPtr< U >& in )
{
// Getting Rid Of The const...
RefCountPtr< U > constHack( in );

// Evil Cast, but i guess it's safe now
m_pRefCountData = reinterpret_cast< RefCountPtr< T >::RefCountData* >( constHack.m_pRefCountData );

m_Ptr = in.m_Ptr;

m_pRefCountData->AddCount();
}



~RefCountPtr()
{
if( m_pRefCountData->RemoveCount() == 0 )
{
delete m_pRefCountData;
delete m_Ptr;
}
}


T* operator->() const
{
return m_Ptr;
}


T& operator*() const
{
return *( m_Ptr );
}


private:

T* m_Ptr;
RefCountData* m_pRefCountData;


private:

class RefCountData
{
public:

RefCountData()
: m_nCount( 0 )
{
}

unsigned int AddCount()
{
return ++m_nCount;
};

unsigned int RemoveCount()
{
return --m_nCount;
};


private:

unsigned int m_nCount;
};

};


struct Base1
{
virtual ~Base1() {};

virtual void Test1() = 0;
};


struct Base2
{
virtual ~Base2() {};

virtual void Test2() = 0;
};


struct Derived : public Base1, public Base2
{
Derived() : Base1(), Base2() {};

virtual ~Derived() {};

virtual void Test1() { std::cout << "Test1\n";};
virtual void Test2() { std::cout << "Test2\n";};
};



int main( int, char** )
{
// Ok
RefCountPtr< Derived > pDerived( new Derived() );
pDerived->Test1();
pDerived->Test2();


// Ok
RefCountPtr< Base1 > pBase1( pDerived );
pBase1->Test1();


// Ok!! :D
RefCountPtr< Base2 > pBase2( pDerived );
pBase2->Test2();


// Output:
//
// Test1
// Test2
// Test1
// Test2


return 0;
}





Share this post


Link to post
Share on other sites
You could also choose to inherit RefCountData<T> from a shared RefCountDataBase class that is independent of T and implements the basic "reference-count" protocol and self-destructs when applicable:


namespace impl {

class RefCountDataBase
{
int refs;
public:
RefCountDataBase() : refs(1) {}
void AddCount() { ++refs; }
void RemoveCount() { if (--refs == 0) delete this; }
virtual ~RefCountDataBase() {}
};

template&lt;typename T&gt;
class RefCountData : public RefCountDataBase
{
std::auto_ptr&lt;T&gt; data;
public:
RefCountData(T *ptr) : data(ptr) {}
};

} // namespace impl


Note that I dropped the "get pointer" member, because you don't need it (the pointer is already available in your pointer classes).

Share this post


Link to post
Share on other sites
Is reinterpret_cast even correct here? Wouldn't dynamic_cast (or static_cast, if type is known at compile time) be more appropriate?

Doesn't reinterpret_cast simply mean "treat X bits as Y bits"? While dynamic_cast deals with all the inheritance checking and whatnot?

Share this post


Link to post
Share on other sites
Hello ZuBsPacE,

Moving the definition of RefCountData out of the templated class RefCountPointer would allow you to lose the reinterpret_cast from your second example (in your most recent post). This could then be placed: at file scope or inside a namespace, For example: namespaced to avoid adding to the global namespace.


namespace RefCountPointerImpl
{
class RefCountData
{
...
};
}

template< typename T >
class RefCountPtr
{
typedef RefCountPointerImpl::RefCountData RefCountData;
...
};




Cheers.

Tom

Share this post


Link to post
Share on other sites
Thank you all for your wonderful suggestions :)

@ToohrVyk: I like your idea, looks very clean and allows me to get rid of the reinterpret_cast!

@ravyne2001: yeah, reinterpret_cast should be avoided if possible. Now I realized that it's cleaner to make unrelated template classes inherit from a common parent class which provides the interface, if possible. dynamic_cast could probably simplify the process, but I'm trying to avoid enabling RTTI.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this