Run-Time Type Checking in C++

Introduction

A frequently asked question is: "How can I identify/check the type of an object in C++ at run-time?" Let me show you by resolving a simple problem!

Complete the program to display "Bark!" in the first call of CFoo::AnimalSays and "Miaou!" in the second one.

class Animal {/*...*/};
class Dog : public Animal {/*...*/};
class Cat : public Animal {/*...*/};
class CFoo
{
public:
  void AnimalSays(Animal*) {/*...*/}
};

int main(int argc, char* argv[])
{
  Dog   rex;
  Cat   kitty;
  CFoo  foo;

  foo.AnimalSays(&rex);
  foo.AnimalSays(&kitty);

  return 0;
}

In other words, you have to find a method to verify at run-time whether the function CFoo::AnimalSays takes, as an argument, a pointer to an object of type Dog or a pointer to an object of type Cat.

One First Try

The first idea is to add a member variable that stores info about the type.

#include <iostream>

class Animal
{
public:
   enum AnimalType {TypeDog, TypeCat};
};

class Dog : public Animal
{
public:
   Dog() : m_type(TypeDog) {}
   const AnimalType m_type;
};

class Cat : public Animal
{
public:
   Cat() : m_type(TypeCat) {}
   const AnimalType m_type;
};

class CFoo
{
public:
   void AnimalSays(Animal*);
};

int main(int argc, char* argv[])
{
   Dog   rex;
   Cat   kitty;
   CFoo  foo;

   foo.AnimalSays(&rex);
   foo.AnimalSays(&kitty);

   return 0;
}

void CFoo::AnimalSays(Animal* pAnimal)
{
   if(((Dog*)pAnimal)->m_type == Animal::TypeDog)
      std::cout << "Bark! ";
   else if(((Cat*)pAnimal)->m_type == Animal::TypeCat)
      std::cout << "Miaou! ";
}

Now, please take a look at the CFoo::AnimalSays function implementation and imagine that you have not only two but fifty-two animal types (in other words, classes derived from Animal)! Quite ugly! It will be hard to write it with no errors and it also will be hard to read/modify/maintain. However, there can be even worse solutions...

Run-Time Type Checking in C++

Try a Somewhat Better One

Keep the class name in a static member variable, accessible with a virtual function.

#include <iostream>
#include <string>

class Animal
{
public:
   virtual bool IsOfType(const std::string&) = 0;
};

class Dog : public Animal
{
   static const string m_class_name;
public:
   virtual bool IsOfType(const std::string&);
};

class Cat : public Animal
{
   static const string m_class_name;
public:
   virtual bool IsOfType(const std::string&);
};

class CFoo
{
public:
   void AnimalSays(Animal*);
};

int main(int argc, char* argv[])
{
   Dog   rex;
   Cat   kitty;
   CFoo  foo;

   foo.AnimalSays(&rex);
   foo.AnimalSays(&kitty);

   return 0;
}

const string  Dog::m_class_name = "Dog";
const string  Cat::m_class_name = "Cat";

bool Dog::IsOfType(const std::string& class_name)
{
   return (class_name == m_class_name);
}

bool Cat::IsOfType(const std::string& class_name)
{
   return (class_name == m_class_name);
}

void CFoo::AnimalSays(Animal* pAnimal)
{
   if(pAnimal->IsOfType("Dog"))
      std::cout << "Bark! ";
   else if(pAnimal->IsOfType("Cat"))
      std::cout << "Miaou! ";
}

CFoo::AnimalSays now looks a little bit clearer and easier to read than the first implementation's try. You still can "simplify" the code by adding two macros, as follows:

#include <iostream>
#include <string>

#define DECLARE_RUNTIME_CLASS \
private: static const std::string m_class_name; \
public: virtual bool IsOfType(const std::string&);

#define IMPLEMENT_RUNTIME_CLASS(class_name) \
const std::string class_name##::m_class_name = #class_name; \
bool class_name##::IsOfType(const std::string& name) \
{return (name == m_class_name);}

// ...

That way, you further can add an even smaller code block:

// ...
class Animal
{
public:
   virtual bool IsOfType(const std::string&) = 0;
};

class Dog : public Animal
{
   DECLARE_RUNTIME_CLASS
};

class Cat : public Animal
{
   DECLARE_RUNTIME_CLASS
};

class CFoo
{
public:
   void AnimalSays(Animal*);
};

int main(int argc, char* argv[])
{
   Dog   rex;
   Cat   kitty;
   CFoo  foo;

   foo.AnimalSays(&rex);
   foo.AnimalSays(&kitty);

   return 0;
}

IMPLEMENT_RUNTIME_CLASS(Dog)
IMPLEMENT_RUNTIME_CLASS(Cat)

void CFoo::AnimalSays(Animal* pAnimal)
{
   if(pAnimal->IsOfType("Dog"))
      std::cout << "Bark! ";
   else if(pAnimal->IsOfType("Cat"))
      std::cout << "Miaou! ";
}

You are now very, very close to the MFC solution.

Run-Time Type Checking in C++

If You Use MFC, It's "a Piece of Cake"

Most MFC classes are derived from CObject. Beside other benefits, CObject together with few MFC macros add run-time type info support. All you have to do to solve your problem the MFC way is to place your classes in the CObject's hierarchy and use the corresponding macros as shown in the next example:

#include <afx.h>

class Animal : public CObject
{
   DECLARE_DYNAMIC(Animal)
};

class Dog : public Animal
{
   DECLARE_DYNAMIC(Dog)
};

class Cat : public Animal
{
   DECLARE_DYNAMIC(Cat)
};

class CFoo
{
public:
   void AnimalSays(Animal*);
};

int main(int argc, char* argv[])
{
   Dog   rex;
   Cat   kitty;
   CFoo  foo;

   foo.AnimalSays(&rex);
   foo.AnimalSays(&kitty);

   return 0;
}

IMPLEMENT_DYNAMIC(Animal, CObject)
IMPLEMENT_DYNAMIC(Dog, Animal)
IMPLEMENT_DYNAMIC(Cat, Animal)

void CFoo::AnimalSays(Animal* pAnimal)
{
   if(pAnimal->IsKindOf(RUNTIME_CLASS(Dog)))
      printf("Bark! ");
   else if(pAnimal->IsKindOf(RUNTIME_CLASS(Cat)))
      printf("Miaou! ");
}

Another Approach: RTTI

A C++ built-in mechanism for run-time type checking is RTTI (Run-Time Type Information). RTTI allows the use of two specific operators: typeid and dynamic_cast. The first solution for solving your problem with RTTI is to use the typeid operator. This returns a referince to a type_info object that keeps info about the type of object passed as a parameter.

#include <iostream>

class Animal {public: virtual ~Animal(){};};
class Dog : public Animal{};
class Cat : public Animal{};
class CFoo
{
public:
   void AnimalSays(Animal*);
};

int main(int argc, char* argv[])
{
   Dog   rex;
   Cat   kitty;
   CFoo  foo;

   foo.AnimalSays(&rex);
   foo.AnimalSays(&kitty);

   return 0;
}

void CFoo::AnimalSays(Animal* pAnimal)
{
   const type_info& ti = typeid(*pAnimal);

   if(ti == typeid(Dog))
      std::cout << "Bark! ";
   else if(ti == typeid(Cat))
      std::cout << "Miaou! ";
}

Nice!

The second RTTI feature you can use is the dynamic_cast operator. This operator returns 0 if it takes a pointer of unwanted type as an argument. So, you can write:

void CFoo::AnimalSays(Animal* pAnimal)
{
   if(dynamic_cast<Dog*>(pAnimal))
      std::cout << "Bark! ";
   else if(dynamic_cast<Cat*>(pAnimal))
      std::cout << "Miaou! ";
}
Note: The Animal class must have at least one virtual function. Why? Because the RTTI mechanism doesn't work if your class isn't of a "polymorphic type"; in other words, it has to have at least one virtual function.
Note: RTTI has to be enabled from the project settings/properties (by default, it's disabled).

At Last: Do You Really, Really Need It?

At last, a good classes design and use of polymorphism can usually make you not even care about run-time type checking. In this particular problem, you can put the pure virtual function ISay in the abstract base class (Animal) and then add a specific "concrete" implementation to each derived one.

#include <iostream>

class Animal
{
public:
   // pure virtual function
   virtual void ISay() = 0;
};

class Dog : public Animal
{
public:
   // specific implementation for Dog
   virtual void ISay() {std::cout << "Bark! ";}
};

class Cat : public Animal
{
public:
   // specific implementation for Cat
   virtual void ISay() {std::cout << "Miaou! ";}
};

class CFoo
{
public:
   void AnimalSays(Animal*);
};

int main(int argc, char* argv[])
{
   Dog   rex;
   Cat   kitty;
   CFoo  foo;

   foo.AnimalSays(&rex);
   foo.AnimalSays(&kitty);

   return 0;
}

void CFoo::AnimalSays(Animal* pAnimal)
{
   pAnimal->ISay();
}

Take one last look at the CFoo::AnimalSays function. It's now clean, simple, and no run-time type checking was necessary.

Conclusion

You can implement run-time type checking by using RTTI or the old MFC-like mechanism, but usually that's not absolutely needed.



About the Author

Ovidiu Cucu

Graduated at "Gh. Asachi" Technical University - Iasi, Romania. Programming in C++ using Microsoft technologies since 1995. Microsoft MVP awardee since 2006. Moderator and article reviewer at Codeguru.com, the number one developer site. Co-founder of Codexpert.ro, a website dedicated to Romanian C++ developers.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today make data protection a must-have, as we live in a data driven society. The digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join eVault Chief Technology …

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds