C++继承 - getClass()等效方法?

11

以以下C++代码为例。

vector<Animal> listAnimal;

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

如果我将它们全部添加到列表中,然后任意从列表中获取它们,我就不知道我正在处理哪个子类。在Java中,我可以使用getClass()thefish instanceof Fish。在C++中该怎么做?


3
为什么你需要知道哪一个?通常情况下,当存储那样的列表时,你会对其中的任何内容调用虚函数,派生类将决定它所做的事情。 - chris
我想我不需要,我可以直接调用它们都从Animal实现的doSomething()函数。 - Andrew
是的,如果你只是在调用它们的函数并希望它们为你分派,下面的答案就可以了。要正确地分派它们,你需要将函数标记为“虚拟的”,并在末尾可选地添加一个“= 0”来使它成为纯函数,这意味着派生类必须为其提供实现。如果不是纯函数,如果派生类没有提供实现,它将使用基类的实现。 - chris
3个回答

14

如果你需要知道正在处理的子类类型,那么就不是正确的多态。多态的全部意义在于减少if语句,使代码变得更加灵活。

有一些情况下你需要知道子类类型,这时可以使用运行时类型信息(RTTI)。但是我建议尽量避免使用它,特别是在需要高性能的场景中(例如游戏或图形程序)。

使用typeid运算符获取有关类的信息,并确定类是否为特定类型。

例如:

Animal* animal1 = new Cat;

if(typeid(animal1) == typeid(Cat))
{
     cout << "animal1 is a: " << typeid(Cat).name();
}
然后使用static_cast将其转换为层次结构中的下一级。
Animal* animal1 = new Cat;

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

或者您可以使用dynamic_cast,它比typeid/static_cast更安全,但速度要慢得多。如下所示:

Animal* animal1 = new Cat;

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

编辑:

dynamic_cast的速度较慢,因为它需要做一些额外的工作,而不仅仅是测试它是否是一个特定类型并进行转换。即dyanmic_cast不等同于typeid/static_cast,但几乎相同。

想象一个超过2级深度的层次结构,例如:

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

假设在Cat类中,有一个特定于所有猫的方法被调用:scratchTheLivingHellOutOfYou()。还假设:我有一个动物列表,并且我想为列表中的每只猫调用scratchTheLivingHellOutOfYou()(这包括从Cat类派生的类)。如果使用typeid运算符和static_cast,那么这将无法实现所需的功能,因为typeid只检查当前类型,不关心层次结构。为此,必须使用dynamic_cast,因为它将检查一个类是否派生自基类,然后相应地向上/向下转换层次结构。

您可以在C ++中查看此简单示例,此处。以下是程序的输出:

USING TYPEID


*scratch*
meoyawnn!
RAWR



USING DYNAMIC_CAST


*scratch*
meoyawnn!
*scratch*
RAWR
因此,您可以清楚地看到dynamic_cast比简单的typeidstatic_cast做了更多的工作。由于dynamic_cast查找层次结构以查看是否为特定类型。简而言之……dynamic_cast可以向上和向下转换层次结构。而typeidstatic_cast只能向下转换层次结构到特定类型。
我想提一下,如果dynamic_cast失败,它将返回一个空指针,或者如果您在引用中使用它,则会抛出异常。
注意:
1.如果您真的需要性能,并且需要检查多态对象的类型,我建议找到RTTI的替代方法,例如使用模板/宏或其他任何标识类的方式。
2.只有在不确定对象将转换为要转换的类型时才应使用dynamic_cast。如果您作为程序员知道任何您要转换的内容100%将成为该类型,则使用static_cast,例如,如果您知道animal1将是Cat,那么static_cast更合适。

1
为什么 dynamic_cast 操作会更慢? - Alex Chamberlain
1
@AlexChamberlain,请查看我在帖子中所做的编辑。 - miguel.martin
你漏掉了dynamic_cast<Cat*>(animal1)中的* - aschepler
@aschepler 哎呀,我的错。 - miguel.martin
然而,我已经尝试过使用 static_cast 并且它的效果与 dynamic_cast 完全相同? - Alex Botev
@Belov 请看我的第二条留言。简而言之,如果您知道对象将是您要转换的类型,请使用 static_cast - miguel.martin

2

一个容器只能存储固定类型的元素,如果你想要一个指向对象的指针,请使用对象指针。

#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 );

Animal类型存储在向量中,当将派生类型推入listAnimal时会导致对象切片

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

编辑:

要知道一个派生动物是什么类型,你可以将它存储在成员中。

Enum AnimalType
{
  FISH,
  MAMAL,
  BIRD
};

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

谢谢你的回答。当我在问题中编写代码时,我有点匆忙,它们是指向事件类的指针。 - Andrew

-1

我通常会创建一个纯虚函数,每个派生类都实现它来告诉你它的身份。例如:

enum AnimalType
{
   Fish = 0,
   Mammal,
   Bird
}

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

...

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

然后你可以像这样做:

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

为什么这个被踩了?这种方法有什么不好的地方吗? - MattCochrane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接