Know the class of a subclass in C++

I haven't done C++ in at least 7 years and am suddenly knee deep in a C++ project. I would like some guidance with the following:

I have a class called Animal, and I have 3 classes that inherit from Animal: Cat, Dog and Bird. I have created a list object and am using it to store type Animal.

This list can contain Cats Dogs and Birds, when I am iterating through this list of Animals, I would like to know the immediate type of each Animal (whether it's a Cat, Dog or Bird).

When I say typeid(animal).name(); it gives me Animal, which is true, but I would like to know what kind of Animal.

Any ideas?? Should I use enums??

Answers


You almost certainly don't want to know. What you should do is declare as virtual appropriate methods to interact with these animals.

If you need to operate on them specifically, you can either use the visitor pattern to pass a visitor object or function the right data in each concrete class. If you insist on having tags (and I emphasise that this is the third choice - the other two solutions will leave your code much cleaner), have a virtual method called classname which returns the type identifier (whether as a string or int or whatever).

Note also the point about slicing if you have an array of object type, as opposed to pointer type. If you haven't used C++ in 7 years, you may not be aware of the growth of template usage to make the language vastly better. Check out libraries like boost to see what can be done, and how templating allows you to write type-inferred generic code.


Dog* dog = dynamic_cast<Dog*>(myAnimalPointer);
if (dog == NULL)
{ 
    // This animal is not a dog.
}

Needing to know a specific concrete object type is typically a design smell in C++ so I'm going to suggest that you don't attempt to do this.

Instead, create an abstract (pure virtual) interface within Animal that describes the functionality you want your animals to have. Then you can utilize dynamic dispatch to call that functionality without even needing to know the dynamic type of the object. You can always create private non-virtual helper functions within the child classes if needed.

Also note that you'll need to store the Animals by (smart) pointer in your container rather than by value. If you store them by value they will all be sliced on insertion into the list, losing the dynamic type information.

And as @Marcin pointed out, using the visitor pattern for double dispatch might be a better approach if you really do need to call specific methods on specific child classes.


Depending on the concrete code typeid returns different things. Additionally name() can return anything (including making the first letter uppercase or removing *), it is only for debuging. Now I have a few different posible answers what typeid(animal).name() can return.

Version 1 animal is a class name:

struct animal {
    virtual ~animal() {}
};

struct dog 
    : animal
{};

struct cat
    : animal
{};

struct bird
    : animal
{};

int main() {
    std::cout << typeid(animal).name() << std::endl; // animal
    return 0;
}

Version 2 animal is a typedef to Animal:

struct Animal {
};

struct Dog 
    : Animal
{};

struct Cat
    : Animal
{};

struct Bird
    : Animal
{};

int main() {
    typedef Animal animal;
    std::cout << typeid(animal).name() << std::endl; // Animal
    return 0;
}

Vesion 3 animal is a pointer:

struct Animal {
};

struct Dog 
    : Animal
{};

struct Cat
    : Animal
{};

struct Bird
    : Animal
{};

int main() {
    Dog d;
    Animal* animal=&d;
    std::cout << typeid(animal).name() << std::endl; // Animal*
    return 0;
}

Version 4 animal is a object:

struct Animal {
};

struct Dog 
    : Animal
{};

struct Cat
    : Animal
{};

struct Bird
    : Animal
{};

int main() {
    Animal animal;
    std::cout << typeid(animal).name() << std::endl; // Animal
    return 0;
}

Version 6 animal is a reference to a non polymorphic objcet:

struct Animal {
};

struct Dog 
    : Animal
{};

struct Cat
    : Animal
{};

struct Bird
    : Animal
{};

int main() {
    Dog d;
    Animal& animal=d;
    std::cout << typeid(animal).name() << std::endl; // Animal
    return 0;
}

and version 7 animal is a reference to a polymorphic object:

struct Animal {
  ~virtual Animal() {}
};

struct Dog 
    : Animal
{};

struct Cat
    : Animal
{};

struct Bird
    : Animal
{};

int main() {
    Dog d;
    Animal& animal=d;
    std::cout << typeid(animal).name() << std::endl; //Dog
    return 0;
}

As others have written it's better not to rely on name(). But without some code it's not easy to say what's correct.


Implement a function name() in each subclass.


Since the list can contain any type of animal I am going to assume that it is a list of pointers. In such case typeid will consider the most derived type of the object if you pass it the dereferenced pointer.

typeid(*animal).name();

Is what you are looking for.


Without using special tricks to give your base class information about derived types, there is no way for it to know what subtype an instance is. The simplest way to do this is, as @Joachim Wuttke suggests, to create a virtual function forcing derived classes to implement a name() method.

However, if you want to get a little fancier, the curiously recurring template parttern CRTP offers a more elegant, if esoteric solution:

#include <typeinfo>
#include <string>
#include <iostream>


template <class T>
class Animal {
public:
    virtual ~Animal() {};  // a base class
    std::string name() {
        return typeid(T).name();
    }
};


class Cat: public Animal<Cat> {

};

class Dog: public Animal<Dog> {

};

int main( int argc, char* argv[] ){
    Cat c;
    Dog d;

    std::cout << c.name() << std::endl;
    std::cout << d.name() << std::endl;

}

result (g++):

3Cat
3Dog

result (vs2008):

class Cat
class Dog

Note that as others have said typeid's name mangling is platform/compiler dependent, so to go from the names above back to the class you have to implement a platform/compiler-dependent demangling routine. Not especially difficult, but it does take away from the elegance of the solution.


Need Your Help

SearchView focused when hiding the ActionBar menu

android search android-actionbar

I have an ActionBar with a SearchView. The hidden/overflow menu items are shown on the right in a drop-down list when the menu button is selected. When the drop-down menu is hidden, the SearchVie...

How to skip the OPTIONS preflight request?

angularjs post cors

I had developed a PhoneGap app which is now being transformed to a mobile website. Everything works smoothly besides one small glitch. I use a certain third party API via a POST request, which work...