I\'m trying to figure out why some code bases use IsA() to determine object polymorphism if in C++ you can already safely upcast and down cast (using dynamic_cast) ?
So
In modern C++ there is no point.
Frameworks dating from before the 1998 standardization may offer an IsA function.
E.g. as I recall there is such functionality in MFC.
Also, as you note, when dealing with objects implemented in other languages (with types not represented by C++ types) it may conceivably be useful.
Cheers & hth.,
There are few reasons where and IsA()
function, or even a dynamic_cast<>()
are needed in C++. The worst examples of this type of code are the giant if-then statements using dynamic_cast
s, or switch statements on a type
field. These represent a maintenance nightmare, where adding a class can involve updating dozens, or hundreds of different locations to support the new class.
For example:
Bad:
// Don't do this:
void PrintName(Base *b, ostream &o)
{
if (dynamic_cast<DerivedA *>(b) != NULL)
o << "Derived A";
if (dynamic_cast<DerivedB *>(b) != NULL)
o << "Derived B";
if (dynamic_cast<DerivedC *>(b) != NULL)
o << "Derived C";
}
Better:
void PrintName(Base *b, ostream &o)
{
o << b->GetName();
}
This is obviously eliding checking for null, and using smart pointers, etc.. Likewise, if you're querying the type to choose between different behaviours, you need to ask why you're doing something different for each type, and move that behaviour decision into the object.
Bad:
// Don't do this:
void ObjectBase::ApplyForceToObject(const Force &f)
{
if (dynamic_cast<Wall*>(this) != NULL
|| dynamic_cast<Floor*>(b) != NULL)
{
// Do nothing
}
else
{
// Accelerate object
}
}
Better:
void ObjectBase::ApplyForceToObject(const Force &f)
{
if (IsFixedObject())
{
// Do nothing
}
else
{
// Accelerate object
}
}
...
bool ObjectBase::IsFixedObject() { return false; }
bool Wall::IsFixedObject() { return true; }
bool Floor::IsFixedObject() { return true; }
Because dynamic-cast relies on RTTI, and this can hurt performance. It is also a bit more convinient and reliable.
Sometimes you dont have RTTI ( maybe you're working on a memory/cpu constrained system and disabling RTTI is the mandated solution) In that case you don't have dynamic_cast available to you. If you want to use something similar to RTTI you usually end up with solutions that are IsA() with static_cast.
Another possible use for an IsA() function is when you don't know all your classes at compile-time (maybe they've been loaded from shared library), or you dont want to list all the types explicitly. This lets you write things like
handleInstance( Instance * i )
{
//libs has been filled through loading dynamic libraries.
for( auto it = libs.begin() ; it!=libs.end() ; ++it )
{
if( i->IsA( it->type_key ) )
{
it->process( i );
}
}
}
Though in this case I might turn the test condition inside out like
if( it->can_process( i ) )
and can_process
would be free to use dynamic_cast
.
An example of how to find run time type information without RTTI
MFC uses a function called IsKindOf() for run time type information. The method MFC uses to find type information is somewhat like the example below.
#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())
#define RUNTIME_OBJ(class_name) (class##class_name)
struct RunTimeClass
{
string name;
};
class base
{
static RunTimeClass RUNTIME_OBJ(base); //watch the naming
public:
bool IsExactKind(RunTimeClass* pRTclass)
{
if(pRTclass == GetRunTimeClass())
{
return true;
}
return false;
}
static RunTimeClass* GetThisClass()
{
return &RUNTIME_OBJ(base);
}
virtual RunTimeClass* GetRunTimeClass()
{
return &RUNTIME_OBJ(base);
}
virtual ~base() = 0;
};
class derived: public base
{
static RunTimeClass RUNTIME_OBJ(derived); //watch the naming
public:
RunTimeClass* GetRunTimeClass()
{
return &RUNTIME_OBJ(derived);
}
static RunTimeClass* GetThisClass()
{
return &RUNTIME_OBJ(derived);
}
};
class derived2: public derived
{
static RunTimeClass RUNTIME_OBJ(derived2); //watch the naming
public:
RunTimeClass* GetRunTimeClass()
{
return &RUNTIME_OBJ(derived2);
}
static RunTimeClass* GetThisClass()
{
return &RUNTIME_OBJ(derived2);
}
};
In the cpp file
RunTimeClass base::classbase = {"base"}; //not using the macro RUNTIME_OBJ
RunTimeClass derived::classderived = {"derived"}; //not using the macro RUNTIME_OBJ
RunTimeClass derived2::classderived2 = {"derived2"}; //not using the macro RUNTIME_OBJ
base::~base() {}
void main()
{
derived *ptrDer = new derived();
bool isder = ptrDer->IsExactKind(RUNTIME_CLASS(derived));
derived2 *ptrDer2 = new derived2();
isder = ptrDer2->IsExactKind(RUNTIME_CLASS(derived2));
delete ptrDer;
delete ptrDer2;
}
Note that this can only find if the object is of the exact class type. To add type information you just need to inherit from base and add a Runtimeclass variable and implement two functions getthisclass(), getruntimeclass(). MFC use CObject as the base class which provides similiar functionality. Also, there are more macros to make your life easier. IsKindOF() function in MFC walk thourgh the entire hierarchy and find out the object "is a" type of class(which is not there in my scaled down version). You can see this is somewhat similiar to RTTI, hence I guess there is no perfomance difference. It is there in MFC because it was there before RTTI.
So, if there is an IsA() function which perfoms better than RTTI, I would like to see the implementation.