14

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??

Marcin
  • 48,559
  • 18
  • 128
  • 201
PaulG
  • 6,920
  • 12
  • 54
  • 98
  • 20
    If at all possible don't try to do this. Create your `Animal` abstract interface so that the concrete type doesn't matter. – Mark B Jul 16 '13 at 14:14
  • 1
    If your list is std::list you won't get polymorphism, you need to use smart pointers – doctorlove Jul 16 '13 at 14:15
  • @MarkB write it in an answer so i can upvote it – No Idea For Name Jul 16 '13 at 14:15
  • 5
    You have a list of Animals or list a *pointers* to Animals? (BTW you shouldn't need to know what kind of animal is an instance in a well-designed interface.) – kennytm Jul 16 '13 at 14:15
  • It's a list of pointers. – PaulG Jul 16 '13 at 14:15
  • 2
    Agree with Mark. But more fundamentally, it sounds like you're storing a list of `Animal` rather than `Animal *`. Once you store a Bird into an Animal instance, it is truly no longer a Bird, but only an Animal. – Peter Jul 16 '13 at 14:16
  • possible duplicate of [How to find out what type of object a pointer points to in C++?](http://stackoverflow.com/questions/17606586/how-to-find-out-what-type-of-object-a-pointer-points-to-in-c) – Jerry Coffin Jul 16 '13 at 14:24
  • @Peter: let us note that it does not occur if you have a pure virtual in `Animal` (the compiler complains the type is abstract) or if you define the copy constructor, move constructor and assignment operators of `Animal` to be `protected` (the compiler complains it cannot access those). – Matthieu M. Jul 16 '13 at 14:25

7 Answers7

22

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.

Marcin
  • 48,559
  • 18
  • 128
  • 201
  • I know true polymorphism would mean not needing to know the concrete class, but for other purposes it would be good to know, I like your virtual typename method, this might work for my needs. – PaulG Jul 16 '13 at 14:21
  • 6
    While I totally agree with your answer, I will still point out that `typename` is a reserved keyword in C++. – Aesthete Jul 16 '13 at 14:21
  • @Aesthete Is that new since 2002? That was the last standard I used. – Marcin Jul 16 '13 at 14:23
  • @Marcin, There is no 2002 standard. It's 98, 03 and 11. It was a keyword in 03. I'm not sure about 98. – OmnipotentEntity Jul 16 '13 at 14:26
  • 1
    @Marcin It's always been there. – Rapptz Jul 16 '13 at 14:27
  • @PaulG If this answer resolved your problem, you might like to accept it. If you found a different resolution, I encourage you to post it and accept that instead. (And the same applies if another answer here was the resolution to your problem). – Marcin Dec 13 '13 at 18:21
18
Dog* dog = dynamic_cast<Dog*>(myAnimalPointer);
if (dog == NULL)
{ 
    // This animal is not a dog.
}
Aesthete
  • 18,622
  • 6
  • 36
  • 45
  • 3
    This is the only actual "answer" to the question so far, I would say. Although the virtual function ideas work too. +1 – Ricky Mutschlechner Jul 16 '13 at 15:01
  • 2
    Or the irresistably streamlined form... `if (Dog* dog = dynamic_cast(myAnimalPointer)) ...`. But, it's not necessarily the *right* answer in general - if later there're further-derived `Doberman` and `Poodle` classes, this code will continue to compile and run without telling the most-derived name. Ok for the very limited hierarchy in the question though. – Tony Delroy Jul 16 '13 at 15:03
  • @RickyMutschlechner, even though this is the direct answer to this question, most everyone else realizes that OP is asking the wrong question. While `dynamic_cast`ing is useful for the Capability Query idiom, it doesn't seem that that particular idiom is relevant from the question asked. – OmnipotentEntity Jul 16 '13 at 21:35
  • This doesn't work if none of the classes have virtual methods. If that's not the case and there's no oop way to avoid it, this is the way to go. – Xavier Arias Botargues Aug 18 '13 at 14:25
11

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.

Mark B
  • 95,107
  • 10
  • 109
  • 188
3

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.

Jan Herrmann
  • 2,717
  • 17
  • 21
2

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.

stonemetal
  • 6,111
  • 23
  • 25
  • 2
    May work, but the textual content of `name()` is implementation defined and allowed to be empty, so it's not reliable/portable. – Tony Delroy Jul 16 '13 at 15:02
1

Implement a function name() in each subclass.

Joachim W
  • 7,290
  • 5
  • 31
  • 59
  • A virtual function?? I have to be able to call this function from my Animal object. – PaulG Jul 16 '13 at 14:19
  • Yes, but don't think this will be the best way. Name is a string, where you have to do a string compare... – RvdK Jul 16 '13 at 14:21
  • This is why I was thinking of enums, but not sure how to get it to work with subclassing. – PaulG Jul 16 '13 at 14:23
  • 1
    Easily, just put the enum in the Animal class, and have your overloaded virtual return the proper value. But the problem with this method is now the Base class needs to know about all of the types of animals, which is bad juju. Because a base class should know as little as possible about things that derive from it. And it violates DRY. – OmnipotentEntity Jul 16 '13 at 14:25
  • 1
    @PaulG Two ways to do it. The First way: in the base class create a field for the enum and set it in all of the constructors. The second way is to create a virtual function that returns the right enum value and override it in all the subclasses to get the right value. – stonemetal Jul 16 '13 at 14:35
0

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.

Dave McMordie
  • 543
  • 4
  • 6