SimpleLib

Dynamic Types

SimpleLib includes a set of classes to provide simple dynamic type support allowing:

  • Querying an object instance to determine if it is of a particular type.
  • Creating object instances given a pointer to its type information.
  • Using type ID's for serializing objects to persistent storage.

Dynamic types can be a little confusing if you've not used them before. I've tried to provide a clear explanation here, but you might need to read this through a few times to properly understand it.

CDynamic Class

The CDynamic class is used to mark an object as supporting SimpleLib's dynamic type system:

template <class TSelf, class TBase=none>
class CDynamic;

To use CDynamic, derive the classes requiring dynamic type info from it. The first template parameter should be name of the class itself. The second template parameter should specify the base class, or omitted if there is no base class.

The following examples define a simple class heirarchy:

class CFruit : public CDynamic<CFruit>
{
};

class CApple : public CDynamic<CApple, CFruit>
{
};

class CBanana : public CDynamic<CBanana, CFruit>
{
};

Once defined, dynamic downcasts can be performed using the As and QueryAs methods. For Example:

CFruit* pFruit;

// Will assert if pFruit is not CBanana ptr
CBanana* pMustBeBanana=pFruit->As<CBanana>();	

// Will return NULL if not a CBanana ptr
CBanana* pMightBeBanana=pFruit->QueryAs<CBanana>();	</pre>

CDynamicCreatable Class

CDynamicCreatable is similar to CDynamic but also offers the ability to instantiate object instances from a type identifier. Declaring classes with CDynamicCreatable is similar to CDynamic.

Classes using CDynamicCreatable must have a parameterless constructor.

class CFruit : public CDynamic<CFruit>
{
};

class CApple : public CDynamicCreatable<CApple, CFruit>
{
};

class CBanana : public CDynamicCreatable<CBanana, CFruit>
{
};

Class types are represented by the CDynType class. To get the CDynType for a particular class use the static method GetType. For example to get the CDynType for CApple:

CDynType* pType=CApple::GetType();

To create an instance of the type represented by a CDynType, use its CreateInstance method:

CFruit* pFruit=(CFruit*)pType->CreateInstance();

To retrieve the CDynType for a CDynamic class use the QueryType method:

CDynType* pType=pFruit->QueryType();

Note the subtle difference between QueryType() and GetType(). QueryType is a virtual function that retrieves the most derived type of an object instance. GetType is a static method that retrieves the type of a class name or pointer type

// This will return CDynType for CFruit (not the derived type)
CDynType* pType=pFruit->GetType();

// and is equivalent to
CDynType* pType=CFruit::GetType();</pre>

Type IDs

A common use of dynamic types is for serialization of object collections to persistent storage. In order to do this, the object types must be represented in a controllable manner that can be written to disk. When loading it must be possible to convert this back to dynamic type info in order to create the correct object instance.

To facilitate this, SimpleLib's dynamic type support includes the ability to associate an integer ID with each class. This can be done in one of two ways. The first and simplest way is via the CDynamicCreatable template's third parameter:

class CFruit : public CDynamic<CFruit>
{
};

class CApple : public CDynamicCreatable<CApple, CFruit, 1>
{
};

class CBanana : public CDynamicCreatable<CBanana, CFruit, 2>
{
};

The second way is to provide a static method on your class called GenerateTypeID(). This allows for more complex methods of allocating type ID's - perhaps based on the class name for example.

class CApple : public CDynamicCreatable<CApple, CFruit>
{
public:
	static int GenerateTypeID() { return 1; }; 
};

To retrieve the type ID of an object instance, use QueryType to get the object's type info, then GetID() to retrieve the ID:

int iID=pFruit->QueryType()->GetID();

To convert a type ID back to a CDynType pointer use CDynType::GetTypeFromID

CDynType* pType=CDynType::GetTypeFromID(iID);

Bringing it All Together

So to bring all this together the following example shows how a collection of objects might be serialized.

It assumes the "fruit" classes defined in the above examples. It also assumes a fictional class CStorage that provides methods for reading and writing to some form of persistent storage.

class CFruits : public CVector<CFruit*, SOwnedPtr> { public:

void Save(CStorage& storage)
{
    // Save number of objects
    storage << GetSize();

    // Save each object
    for (int i=0; i<GetSize(); i++)
    {
         // Save the object's type ID
         storage << GetAt(i)->QueryType()->GetID();

         // Save the object
         GetAt(i)->Save(storage);
    }
}

void Load(CStorage& storage)
{
	// Load number of objects
	int iCount;
	storage >> iCount;

	// Load objects
	for (int i=0; i<iCount; i++)
	{
          // Load object's type ID
          int iID;
          storage >> iID;

          // Get the type
          CDynType* pType=CDynType::GetTypeFromID(iID);

          // Create the object
          CFruit* pFruit=(CFruit*)pType->CreateInstance();

          // Load the object
          pFruit->Load(storage);

          // Add to collection
          Add(pFruit);
	}
}

Runtime Class Names

CDynamic now supports dynamic type names. To declare the name of your class, simply add a static method GetTypeName to your class that returns the class name:

class CApple : public CDynamicCreatable<CApple, CFruit>
{
public:
	static const wchar_t* GetTypeName() { return L"CApple"; }; 
};

You can then discover the type info for this class by name:

CDynType* pType=CDynType::GetTypeFromName(L"CApple");

or, given an object instance, get its type name:

pApple->QueryType()->GetName()

This approach is excellent where you want to be able to create object instances given a string name of the class and is particularly well suited to persistence in text formats (eg: xml).