Templates And Casting
If I have a template class, a base class, a derived class, and a function that takes the template of the base:
class Base { };
class Derived : public Base { };
template < typename T > class Pointer { };
void Function ( Pointer < Base > B ) { }
int main ( )
{
Pointer < Derived > A;
Function ( A );
}
Is there any way I can make "Function ( A );" work automatically?
Is this possible?
Thanks!
Greg
The way boost does this for it's smart pointer types is by giving Pointer a constructor ala:
template < typename T > class Pointer { T* internal;public: template < typename U > Pointer( const Pointer<U>& other ): internal(other.internal) {}};
You can do this without drilling into the implementation of your other Pointer template instances by assuming a uniform interface.
Ie:
The fundamental difference is that it allows someone to specialise Pointer to use some variable other than T* internal. So long as BasePointer::create< U > can create your Pointers from a U*, it works. (I left that unimplemented).
If your Pointer<T> can be trivially constructed from a T*, then you don't even need the BasePointer step -- but if Pointer<T> is anything like a reference counting pointer, then having it silently either take a reference to a T* or add a reference to a T* is fraught with issues.
Ie:
template<typename T>class Pointer;class BasePointer{ template<typename T> Pointer<T> create( T* ); // or whatever};template<typename T>class Pointer: public BasePointer{ T* internal; friend class BasePointer;public: template<typename U> operator Pointer<U>() { BasePointer::create<U>( internal ); }};
The fundamental difference is that it allows someone to specialise Pointer to use some variable other than T* internal. So long as BasePointer::create< U > can create your Pointers from a U*, it works. (I left that unimplemented).
If your Pointer<T> can be trivially constructed from a T*, then you don't even need the BasePointer step -- but if Pointer<T> is anything like a reference counting pointer, then having it silently either take a reference to a T* or add a reference to a T* is fraught with issues.
Awesome!
I think I now have a pretty good reference counter. Any thing that can be counted must inherit "Pointable", but that's nbd. :)
Thanks!
Greg
I think I now have a pretty good reference counter. Any thing that can be counted must inherit "Pointable", but that's nbd. :)
Thanks!
Greg
#pragma oncetemplate < typename T > class Pointer;// This is a nice reference counting pointer. Any class that you want to have be// to be pointed to must publicly inherit Pointable. Then a pointer to the object can be // made with Pointer<Thing>Name. Note that it must be constructed with the item // it is pointing to, so if making a dynamic object, use:// Pointer<Thing>Name = Pointer<Thing>::New (params)class Pointable{ template<typename T> friend class Pointer; unsigned int NumRefs; void IncRefs ( ) { NumRefs++; } void DecRefs ( ) { assert ( NumRefs > 0 ); NumRefs--; }public: Pointable ( ) : NumRefs ( 0 ) {} // Copy constructor Pointable ( const Pointable & NewP) : NumRefs ( 0 ) {}};template < typename T > class Pointer{ template <typename TT> friend class Pointer; T * Ref;public: Pointer ( ) : Ref ( NULL ) {} Pointer ( T & obj ) : Ref ( & obj ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( T * obj ) : Ref ( obj ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( Pointer<T> & rVar ) : Ref ( rVar.Ref ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( Pointer<T> * rVar ) : Ref ( rVar->Ref ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( const Pointer<T> & rVar ) : Ref ( rVar.Ref ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( const Pointer<T> * rVar ) : Ref ( rVar->Ref ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } template < typename U > Pointer ( const Pointer<U> & rVar ) : Ref ( rVar.Ref ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } template < typename U > Pointer ( const Pointer<U> * rVar ) : Ref ( rVar->Ref ) { //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } ~Pointer ( ) { //fprintf(stderr, "%i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); if ( Ref != NULL ) { Ref->DecRefs ( ); if ( Ref->NumRefs < 1 ) { //fprintf ( stderr, " DELETED\n" ); delete Ref; } } } T * operator-> ( ) const { return Ref; } T * GetUnprotectedPointer ( ) const { return Ref; } T & GetUnprotectedRef ( ) const { return *Ref; } Pointer & operator = ( const Pointer<T> * rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "%i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); if ( Ref->NumRefs < 1 ) { //fprintf ( stderr, " DELETED\n" ); delete Ref; } } Ref = rVar->Ref; //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } Pointer & operator = ( const Pointer<T> & rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "%i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); if ( Ref->NumRefs < 1 ) { //fprintf ( stderr, " DELETED\n" ); delete Ref; } } Ref = rVar.Ref; //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } Pointer & operator = ( const T * rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "%i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); if ( Ref->NumRefs < 1 ) { //fprintf ( stderr, " DELETED\n" ); delete Ref; } } Ref = rVar; //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } Pointer & operator = ( const T & rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "%i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); if ( Ref->NumRefs < 1 ) { //fprintf ( stderr, " DELETED\n" ); delete Ref; } } Ref = &rVar; //fprintf(stderr, "%i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } friend bool operator == ( const Pointer<T> & lhs, const Pointer<T> & rhs ) { return ( lhs.Ref == rhs.Ref ); } template <typename... Args> static Pointer < T > New (Args&&... params) { return Pointer < T > ( new T ( (params)... ) ); } static Pointer < T > New ( Pointer < T > Ti) { return Pointer < T > ( new T ( *Ti.Ref ) ); }};template < typename T >class Newable{public: template <typename... Args> static Pointer < T > New (Args&&... params) { return Pointer < T > ( new T ( (params)... ) ); }};
Your code isn't remotely thread safe: and that is a rather important property. Even if you don't need it yet, do the make-work to make it possible.
Generally you want your inc and dec refs to return the value of the reference count immediately after you did the add/remove ref.
Then the code that calls add/remove ref takes the return value, and makes a decision based on this.
Second, I would move more of the implementation from the Pointer<> class to the Pointable<> class. Make the Pointer<> class a simple RAII addref/removeref class that isn't in charge of deleting the Pointable<> object.
Make the Pointable<> class have a virtual void DestroySelf() { delete this; } method.
Have int DecRefs() do the following:
From the above, a fully thread-safe implementation is a mere "substitute operator -- for a thread-safe function" away.
In addition, the DestroySelf() method allows you to create reference counted objects that pool themselves, have a pre-destructor phase, or a number of other tricks. And by default, call delete.
There is even the simple:
which creates an on-stack Pointable object type, in case you need it for whatever reason. :)
Generally you want your inc and dec refs to return the value of the reference count immediately after you did the add/remove ref.
Then the code that calls add/remove ref takes the return value, and makes a decision based on this.
Second, I would move more of the implementation from the Pointer<> class to the Pointable<> class. Make the Pointer<> class a simple RAII addref/removeref class that isn't in charge of deleting the Pointable<> object.
Make the Pointable<> class have a virtual void DestroySelf() { delete this; } method.
Have int DecRefs() do the following:
int retval = --NumRefs;if (!retval) { DestroySelf();}assert(retval >= 0);return retval;
From the above, a fully thread-safe implementation is a mere "substitute operator -- for a thread-safe function" away.
In addition, the DestroySelf() method allows you to create reference counted objects that pool themselves, have a pre-destructor phase, or a number of other tricks. And by default, call delete.
There is even the simple:
template<typename Pointable>class OnStack: public Pointable { void DestroySelf() { assert(false); }public: virtual ~OnStack() { assert(NumRefs == 1); }};
which creates an on-stack Pointable object type, in case you need it for whatever reason. :)
Okay, I think I have it now that I can add thread locks in the future, whenever that time may come ( I still haven't decided between SDLthreads and pthreads ). I made the changes you recommended and it definitely feels more elegant. I had one bug that was really making me scratch my head, and that was that my objects were being deleted, but the destructors weren't being called, and that was because I didn't have an empty virtual destructor in Pointable!
Do you see anything else that might potentially be a problematic?
Thanks!
Greg
Do you see anything else that might potentially be a problematic?
Thanks!
Greg
#pragma oncetemplate < typename T > class Pointer;// This is a nice reference counting pointer. Any class that you want to have able// to be pointed to must publically inheirit Pointable. Then a pointer to the object can be // made with Pointer<Thing>Name. Note that it must be constructed with the item // it is pointing to, or assigned. If making a dynamic object, use:// Pointer<Thing>Name = Pointer<Thing>::New (params)class Pointable{ template < typename T > friend class Pointer; unsigned int IncRefs ( ) { return ++NumRefs; } unsigned int DecRefs ( ) { int retval = --NumRefs; if (!retval) { //fprintf ( stderr, " DELETED\n" ); DestroySelf(); } assert(retval >= 0); return retval; } virtual void DestroySelf() { delete this; } protected: unsigned int NumRefs;public: Pointable ( ) : NumRefs ( 0 ) {} // Copy constructor Pointable ( const Pointable & NewP) : NumRefs ( 0 ) {} virtual ~Pointable ( ) { assert ( NumRefs == 0); }};template < typename T > class Pointer{ template <typename TT> friend class Pointer; T * Ref;public: Pointer ( ) : Ref ( NULL ) {} Pointer ( T & obj ) : Ref ( & obj ) { //fprintf(stderr, "& i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( T * obj ) : Ref ( obj ) { //fprintf(stderr, "* %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( const Pointer<T> & rVar ) : Ref ( rVar.Ref ) { //fprintf(stderr, "p& %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } Pointer ( const Pointer<T> * rVar ) : Ref ( rVar->Ref ) { //fprintf(stderr, "p* %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } template < typename U > Pointer ( const Pointer<U> & rVar ) : Ref ( rVar.Ref ) { //fprintf(stderr, "pu& %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } template < typename U > Pointer ( const Pointer<U> * rVar ) : Ref ( rVar->Ref ) { //fprintf(stderr, "pu* %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); } ~Pointer ( ) { //fprintf(stderr, "~ %i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); if ( Ref != NULL ) Ref->DecRefs ( ); } T * operator-> ( ) const { return Ref; } T * GetUnprotectedPointer ( ) const { return Ref; } T & GetUnprotectedRef ( ) const { return *Ref; } Pointer & operator = ( T * rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "=* %i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); } Ref = rVar; //fprintf(stderr, "=* %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } Pointer & operator = ( T & rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "=& %i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); } Ref = &rVar; //fprintf(stderr, "=& %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } Pointer & operator = ( const Pointer<T> * rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "=p* %i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); } Ref = rVar->Ref; //fprintf(stderr, "=p* %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } Pointer & operator = ( const Pointer<T> & rVar ) { if ( Ref != NULL ) { //fprintf(stderr, "=p& %i-- : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->DecRefs ( ); } Ref = rVar.Ref; //fprintf(stderr, "=p& %i++ : %s : %p\n", Ref->NumRefs, typeid(this).name(), Ref); Ref->IncRefs ( ); return *this; } friend bool operator == ( const Pointer<T> & lhs, const Pointer<T> & rhs ) { return ( lhs.Ref == rhs.Ref ); } template <typename... Args> static Pointer < T > New (Args&&... params) { return Pointer < T > ( new T ( (params)... ) ); } static Pointer < T > New ( Pointer < T > Ti) { return Pointer < T > ( new T ( *Ti.Ref ) ); }};template < typename T >class OnStack: public T, public Pointable { void DestroySelf() { assert ( FALSE ); }public: virtual ~OnStack ( ) { assert ( NumRefs == 1 ); }};
Your operator= sucks. :)
Here is the one true operator=(tm):
Well, actually, there are two true operator=s:
Now all you have to write is non-explicit copy constructors and a method swap(foo&) that swaps the content of two instances of foo.
And operator= just works.
Your constructors should be the following:
Your other constructors are a bad idea.
GetUnprotectedPointer should be renamed to "BorrowPointer", because that is what you are doing.
Possibly you should support operator T* (and maybe operator U*) implicit casting. This is useful when you have a function that doesn't know it is taking a reference counted object.
C++0x I assume?
OnStack should assume the typed passed in was Pointable.
You might want a T* ClonePointer() method, possibly private. It makes a bunch of the rest of the reference counting code easier.
Another useful one for auto pointer classes is
which works well with the interfaces that take a T** or the like and want to fill it. But I suppose they can take a Pointer<T>* just as easily.
Here is the one true operator=(tm):
foo& operator=(otherT const& other){ foo tmp = other; this->swap(tmp); return *this;}
Well, actually, there are two true operator=s:
foo& operator=(foo const& other){ foo tmp = other; this->swap(tmp); return *this;}template<typename T>foo& operator=(T const& other){ foo tmp = other; this->swap(tmp); return *this;}
Now all you have to write is non-explicit copy constructors and a method swap(foo&) that swaps the content of two instances of foo.
And operator= just works.
Your constructors should be the following:
Pointer() {};Pointer(Pointer const& other);template<typename U>Pointer(Pointer< U > const& other);template<typename U>Pointer(U* other);
Your other constructors are a bad idea.
//This operator: T * operator->() const//and this operator: T& operator*() const//should be implemented in terms of: T * GetUnprotectedPointer ( ) const T & GetUnprotectedRef ( ) const
GetUnprotectedPointer should be renamed to "BorrowPointer", because that is what you are doing.
Possibly you should support operator T* (and maybe operator U*) implicit casting. This is useful when you have a function that doesn't know it is taking a reference counted object.
template <typename... Args> static
C++0x I assume?
OnStack should assume the typed passed in was Pointable.
You might want a T* ClonePointer() method, possibly private. It makes a bunch of the rest of the reference counting code easier.
Another useful one for auto pointer classes is
T*& FillPtr() { Pointer tmp; this->swap(tmp); return Ref; }
which works well with the interfaces that take a T** or the like and want to fill it. But I suppose they can take a Pointer<T>* just as easily.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement