C++ has two notions of polymorphism, compile-time and runtime.
Compile-time polymorphism (aka generic programming) is implemented in
terms of templates, and allows the provision of code that can work on
many different types of objects. On the other hand, runtime
polymorphism involves the use of virtual member functions. Whereever generic programming can
solve a task, it is preferred over runtime polymorphism, as virtual
member functions introduce procedure call overhead, and inhibit
optimisation. Furthermore, the use of a copyable, assignable and
serialisable class like poly introduces additional overheads.
Nevertheless, there are situations that cannot be solve with
compile-time polymorphism, for example a container containing objects
of varying types. For this purpose, Classdesc's
poly type is useful. To use poly, your
object heirarchy must implement the following interface (provided as
an abstract base class object).
struct object
{
typedef int TypeID;
virtual TypeID type() const=0;
virtual object* clone() const=0;
virtual void pack(pack_t *b) const=0;
virtual void unpack(pack_t *b)=0;
virtual ~object() {}
};
The type() method implements a simple runtime type identifier
system. In the case of object, it uses simple integer tags,
which are assumed to be allocated more or less consequitively to types
in the type heirarchy. However, any type may be used provided it is
exported as the typedef TypeID, and an appropriate customised type table
class is defined (see below).
It is not actually necessary to use this ABC to use poly. The base
class (which must be default constructible, hence not abstract) is
passed to the poly template. Classdesc provides an empty
concrete class Eobject which can be used for this purpose.
To assist in deriving classes from object, the Object template
is provided.
template <class This, int Type, class Base=object> struct Object;The first template argument
This is the class you're currently defining,
the second (Type) is the integer value of its type tag and
Base is the base class you are deriving from. Eobject is
defined as
class Eobject: public Object<Eobject,0> {};
and a new class (eg foo) with type ID 1 can be defined
class foo: public Object<foo,1,Eobject> {...
This saves having to explicitly provide versions of the virtual
functions type(), clone(), pack() and
unpack(). It also provides a utility method cloneT()
which executes clone(), but instead of returning a bare
object pointer, returns a pointer to an object of the same type
as the calling object (if legally convertible via dynamic_cast).
The synopsis of poly is:
template <class T=Eobject, class TT=SimpleTypeTable<T> >
class poly
{
public:
TT TypeTable;
poly();
poly(const polyref& x);
poly(const T& x);
poly& operator=(const poly& x);
poly& operator=(const T& x);
template <class U> void addObject();
template <class U, class A> void addObject(A);
template <class U, class A1, class A2> void addObject(A1, A2);
T* operator->();
T& operator*();
const T* operator->() const;
const T& operator*() const;
template <class U> U& cast();
template <class U> const U& cast();
void swap(poly& x);
};
Most of this is fairly straightforward. However the addObject()
and cast() methods need a little more explanation. To make the poly
object an object of type (say foobar), use the following calls:
poly.addObject<foobar>(); //calls foobar() poly.addObject(1); //calls foobar(1) poly.addObject(1,"hello"); //calls foobar(1,"hello"); poly.addObject(foobar(x,y,z)); //more than 2 arguments
The cast method provides a convenient method casting the poly
object to a specific type. It is equivalent to calling
dynamic_cast, but a little easier to use, ie
poly.cast<foobar>().grunge() <=> dynamic_cast<foobar&>(*poly).grunge();The return type was chosen to be a reference, not a pointer, as this is the more convenient form. It can easily be converted to a pointer with the
& operator.
The TypeTable member of poly must implement the following interface
class typetable
{
Base& operator[](TypeID);
void register_type(const Base&);
};
where Base is the base type of the poly class, and is
basically a database of reference objects, from which new objects can
be constructed using clone(), given a type identifier. This is
used for implementing serialisation. Classdesc provides simple
implementation of this as SimpleTypeTable<Base>, where the
TypeIDs are integers that are reasonably close to each other.
The polyref type combines ref and poly classes
to give a reference counted polymorphic class. Plus the object
heirarchy above can be combined easily with
std::tr1::shared_ptr to implement a serialisable reference counted
polymorphic smart pointer as an alternative to polyref.