C++ inheritance - getClass() equivalent?

2020-07-06 06:23发布

问题:

Take the following C++ for example.

vector<Animal> listAnimal;

class Fish : Animal ...
class Mammal : Animal ...
class Bird : Animal ...

If I then add them all to the list, and then grab them off the list arbitrary I wont know which subclass I am dealing with. I in Java I could do getClass() or thefish instanceof Fish. How do I do this in C++?

回答1:

You shouldn't need to know what type of sub-class you're dealing with. You're not doing polymorphism right if you need to check the type of class you're dealing with. The whole point of polymorphism is to reduce if's and make your code a lot more flexible.

There are some cases where you need to know, and you can use RTTI for that. However I recommend not to, especially if you require a lot of performance (such as games or graphics programs).

Use the typeid operator to get information about a class, and to determine if a class is a specific type.

For example:

Animal* animal1 = new Cat;

if(typeid(animal1) == typeid(Cat))
{
     cout << "animal1 is a: " << typeid(Cat).name();
}

Then use a static_cast to cast it down the hierarchy.

Animal* animal1 = new Cat;

if(typeid(animal1) == typeid(Cat))
{
     Cat* cat = static_cast<Cat*>(animal1);
     cat->scratchTheLivingHellOutOfYou();
}

Alternatively you can use a dynamic_cast which is much safer, but much slower than a typeid/static_cast. Like so:

Animal* animal1 = new Cat;

if(Cat* cat = dynamic_cast<Cat*>(animal1)
{
     cat->scratchTheLivingHellOutOfYou();
}

EDIT:

dynamic_cast is slower simply because it has to do a little extra work than just testing if it's a specific type and casting. i.e. dyanmic_cast is not equivalent to typeid/static_cast, but it almost is.

Imagine a hierarchy further than 2 levels deep, for example:

class Animal { /* ... */ }; // Base
class Cat : public Animal { /* ... */ }; // 2nd level
class Tiger : public Cat { /* ... */ }; // 3rd level

Let's say that in the Cat class, a method specific to all Cats is called: scratchTheLivingHellOutOfYou(). Let's also say that: I have a list of Animals and I want to call scratchTheLivingHellOutOfYou() for every Cat in the list (this includes classes that derive from the class Cat). If the typeid operator and static_cast is used, this would not achieve what is required, since typeid only checks for the current type and does not care about the hierarchy. For this, you have to use a dynamic_cast, since it will check if a class is derived from a base-class, and then cast up/down the hierarchy accordingly.

You can see this simple example, in C++, here. Here is the output of the program:

USING TYPEID


*scratch*
meoyawnn!
RAWR



USING DYNAMIC_CAST


*scratch*
meoyawnn!
*scratch*
RAWR

Therefore, you can clearly see that dynamic_cast does a lot more work than a simple typeid and static_cast. Since dynamic_cast looks up the hierarchy to see if it is a specific type. Simply put... dynamic_cast can cast up and down the hierarchy. Whereas a typeid and static_cast can only cast down the hierarchy to a specific type.

I thought I'd mention that if dynamic_cast fails it will return a NULL pointer, or throw an exception if you're using it with references.

NOTES:

  1. If you really need performance and you need to check types of polymorphic objects, I recommend finding an alternative to RTTI, such as using templates/macros or whatever else to identify classes.
  2. dynamic_cast should only be used if you're not sure the object will be the type you're converting to. If you, as a programmer, know whatever you're casting is 100% going to be that type then use static_cast, e.g. if you know animal1 is going to be a Cat then static_cast is more appropriate.


回答2:

A container only stores elements of a fixed type, you want a pointer to an object.

#include <memory>
#include <vector>

std::vector<std::unique_ptr<Animal>> animal_list;

animal_list.emplace_back(new Fish);
animal_list.emplace_back(new Mammal);
animal_list.emplace_back(new Bird );

Store Animal type in vector will cause object slice when push derived types into listAnimal.

 vector<Animal> listAnimal;
 listAnimal.push_back(Fish);  // Fish is sliced to Animal, no fish for you.

Edit:

To know what type a derived animal is, you could store it in a member

Enum AnimalType
{
  FISH,
  MAMAL,
  BIRD
};

class Animal
{
public:
  Animal(AnimalType animal_type) : type(animal_type) {}
  AnimalType GetType() const { return type; }
private:
  AnimalType type;   
};


回答3:

I typically create a pure virtual function that each derived class implements to tell you its identity. Example:

enum AnimalType
{
   Fish = 0,
   Mammal,
   Bird
}

class Animal
{
   virtual AnimalType GetType() const = 0;
}

...

AnimalType Bird::GetType()
{
   return Bird;
}

Then you can do something like this:

if (animal.GetType() == Bird)
{
   // ...
}