|
Using Static Members For RTTI Without RTTI
Submitted by |
I recently stumbled upon an interesting dilemma: I am working on some code that
can benefit from RTTI, but the code base does not use RTTI, to enforce type
safety at runtime. Because I had been recently playing around how to fake
template member explicit specializations in Visual C++ 6.0 utilizing a static
member pointer to the class type, somehow it ocurred to me the idea that I could
use the address of said pointer, as an implicit type id for the class.
In other words, because all static members have to go somewhere in memory, it
ocurred to me that static members of different classes have to have different
addresses. We can take advantage of that information to "automatically
generate" an implicit class type id.
So here is the code. Please refer to the comments in the code to continue the
"tip of the day" text and afterwards...
// rhmRtti.cpp : Defines the entry point for the console application.
//
#include <stdafx.h
#include <iostream
#include <string
#define NULL 0
//////////////////////////////////////////
// BASE CLASS
//////////////////////////////////////////
class I_TypeInfoClass
{
public:
// public stuff here
// This template accepts a reference, of a const pointer of type const T
// and the template itself is a const function
// Note: Visual C++ 6.0 requires the template member definition inside
// the class declaration. I.e. no separate definition outside of the
// class { }; declaration is allowed. Visual C++ 7.0 (.NET) seems to
// handle it fine.
template <typename T
bool IsOfExactType(const T * const & refClassType) const
{
return GetClassTypeAddress() ==
reinterpret_cast<unsigned long(&refClassType);
}
// This template may be unnecessary under another implementation
// but I decided to use it because it elimates code in the subclasses
// It's all a matter of taste though, imho.
protected:
// enforce that the subclasses have to implement this method
virtual unsigned long GetClassTypeAddress() const = 0;
};
/////////////////////////////////////////
// SUB CLASS A
/////////////////////////////////////////
class SubClassA: public I_TypeInfoClass
{
public:
// We make this guy a const * that points to a const SubClass A
// because we do not want the user to change it and call on
// potentially anything. Since it points to NULL it would crash though
static const SubClassA * const mClassType;
protected:
// The keyword virtual in this statement is optional,
// because once a base class defines a virtual function
// the derived classes that override it will always be virtual
// also, but I want to be descriptive :-)
virtual unsigned long GetClassTypeAddress () const
{
return reinterpret_cast<unsigned long(&mClassType);
}
// private code & stuff here
};
// It really does not matter, to what this pointer points to
// but let's make it point to NULL since it is true we point
// to no valid object :-)
const SubClassA * const SubClassA::mClassType = NULL;
///////////////////////////////////////////
// SUB-SUB CLASS C
///////////////////////////////////////////
class SubSubClassC: public SubClassA
{
public:
// We make this guy a const * that points to a const SubClass A
// because we do not want the user to change it and call on
// potentially anything. Since it points to NULL it would crash though
static const SubSubClassC * const mClassType;
protected:
virtual unsigned long GetClassTypeAddress () const
{
return reinterpret_cast<unsigned long(&mClassType);
}
// private code & stuff here
};
const SubSubClassC * const SubSubClassC::mClassType = NULL;
///////////////////////////////////////////////////////
// Simple utility function to print status type checks for this
// example.
///////////////////////////////////////////////////////
void PrintTypeCheck(I_TypeInfoClass *pI_TypeInfoClass, std::string &refMessageString)
{
std::cout << "************" << refMessageString << std::endl << std::endl;
// Notice how we check for the exact type - we pass as a parameter
// the static member that is a pointer of the class that we check against
std::cout << "pI_TypeInfoClass..." << std::endl;
if (pI_TypeInfoClass-IsOfExactType(SubClassA::mClassType) )
std::cout << "... points to an exact type SubClassA" << std::endl;
else
std::cout << "... doesn't point an exact type SubClassA" << std::endl;
if ( pI_TypeInfoClass-IsOfExactType(SubSubClassC::mClassType) )
std::cout << "... points to an exact type SubSubClassC" << std::endl;
else
std::cout << "... doesn't point an exact type SubSubClassC" << std::endl;
std::cout << "***********************************" << std::endl << std::endl;
}
///////////////////////////////////////////
// MAIN - duh! :-)
///////////////////////////////////////////
int main(int argc, char* argv[])
{
// First some code setup for the example
SubClassA anA;
SubSubClassC aC;
SubClassA *pA = &anA;
I_TypeInfoClass *pB = &anA;
SubSubClassC *pC = &aC;
// check when pA points to SubClassA
PrintTypeCheck(pA, std::string("When pA points to SubClassA..."));
pA = &aC;
// check when pA points to SubSubClassC
PrintTypeCheck(pA, std::string("When pA points to SubSubClassC..."));
// check when pB points to SubClassA
PrintTypeCheck(pB, std::string("When pB points to SubClassA..."));
pB = &aC;
// check when pB points to SubSubClassC
PrintTypeCheck(pB, std::string("When pB points to SubSubClassC..."));
// check pC just for kicks
PrintTypeCheck(pC, std::string("When pC points to SubSubClassC..."));
return 0;
}
///////////////////////////////////////////////////////////////////
// SAMPLE OUTPUT:
///////////////////////////////////////////////////////////////////
/*
************When pA points to SubClassA...
pI_TypeInfoClass...
... points to an exact type SubClassA
... doesn't point an exact type SubSubClassC
************When pA points to SubSubClassC...
pI_TypeInfoClass...
... doesn't point an exact type SubClassA
... points to an exact type SubSubClassC
************When pB points to SubClassA...
pI_TypeInfoClass...
... points to an exact type SubClassA
... doesn't point an exact type SubSubClassC
************When pB points to SubSubClassC...
pI_TypeInfoClass...
... doesn't point an exact type SubClassA
... points to an exact type SubSubClassC
************When pC points to SubSubClassC...
pI_TypeInfoClass...
... doesn't point an exact type SubClassA
... points to an exact type SubSubClassC
Press any key to continue
*/ |
Couple of comments & improvements:
1. You can define declaration and implementation macros, to avoid more typing
2. This scheme only supports IsOfExactType, not IsOfType. The first will
return true if and only if, the class type that is checked against is exactly
the same type. The later takes into account class hierarchy - so for example,
SubSubClassC would return true on a call to IsOfType(SubClassA::mClassType) but
false on a call to IsOfExactType(SubClassA::mClassType).
You can implement IsOfType by making each class start "asking up the
hierarchy" if the static member adresses match, if the original comparison with
the current class fails. This requires the subclasses to implement more code
and it is highly recommended you do some declaration and implementation macros.
As a quick snippet of code, here's how the I_TypeInfoClass would have to
implement and what the other classes would have to implement, something like
IsTypeOf:
I_TypeInfoClass modifications/additions:
public:
static const I_TypeInfoClass * const mClassType;
template <typename T
bool IsTypeOf(const T * const & refClassType) const
{
// Let's use a variable for clarity
unsigned long refClassTypeAddress = reinterpret_cast<unsigned long(&refClassType);
// Query up the hierarchy if necessary
return GetClassTypeAddress() == refClassTypeAddress ? true : IsOfBaseType(refClassTypeAddress);
}
protected:
virtual bool IsOfBaseType(unsigned long pasUpAddress) const
{
return reinterpret_cast<unsigned long(&mClassType) == pasUpAddress;
}
virtual unsigned long GetClassTypeAddress () const
{
return reinterpret_cast<unsigned long(&mClassType);
}
SubClassA modifications/additions:
public:
static SubClassA * const mClassType;
protected:
virtual bool IsOfBaseType(unsigned long pasUpAddress) const
{
// Query up the hierarchy if necessary
return reinterpret_cast<unsigned long(&mClassType) == pasUpAddress ?
true : I_TypeInfoClass::IsOfBaseType(pasUpAddress);
}
virtual unsigned long GetClassTypeAddress () const
{
return reinterpret_cast<unsigned long(&mClassType);
} |
Note that the snippets of code were taking out of working code and slightly
modified, so there's a slight change they won't compile :-) I also used the
classes I_TypeInfoClass and SubClassA in the spirit of continuing the example
code, but you certainly do not have to use such names.
3. I am sure many of you will pick on more details :-)
Download the example project here: rhmrtti.zip (6k)
Comments welcome. Credit goes to Steve Anichini for letting me know how to F@$#
get Visual C++ 6.0 to do pseudo template member explicit instantiations.
- Raist3d
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|