next up previous contents index
Next: Packing to a file Up: pack/unpack Previous: Synopsis of pack_t   Contents   Index


Polymorphism

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.


next up previous contents index
Next: Packing to a file Up: pack/unpack Previous: Synopsis of pack_t   Contents   Index
Russell Standish 2009-06-18