知道C++中一个子类的类

Know the class of a subclass in C++

本文关键字:一个 子类 C++ 知道      更新时间:2023-10-16

我至少有7年没有做C++了,突然陷入了一个C++项目。我想要一些以下方面的指导:

我有一个名为Animal的类,我有三个从Animal继承的类:猫、狗和鸟。我已经创建了一个列表对象,并且正在使用它来存储Animal类型。

这个列表可以包含猫、狗和鸟,当我迭代这个动物列表时,我想知道每个动物的直接类型(无论是猫、狗还是鸟)。

当我说typeid(animal).name();时,它给了我动物,这是真的,但我想知道什么样的动物。

有什么想法吗??我应该使用enums吗??

您几乎肯定不想知道。你应该做的是宣布与这些动物互动的虚拟适当方法。

如果您需要对它们进行特定操作,您可以使用访问者模式来传递访问者对象,也可以在每个具体类中使用正确的数据。如果您坚持使用标记(我强调这是第三种选择-其他两种解决方案会让您的代码更干净),那么使用一个名为classname的虚拟方法,它会返回类型标识符(无论是字符串、int还是其他形式)。

如果您有一个对象类型的数组,而不是指针类型,请注意关于切片的要点。如果你已经7年没有使用C++了,你可能没有意识到模板使用量的增长会让语言变得更好。看看boost之类的库,看看可以做些什么,以及模板化如何允许您编写类型推断的泛型代码。

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

在C++中,需要知道特定的具体对象类型通常是一种设计气味,所以我建议您不要尝试这样做。

相反,在Animal中创建一个抽象(纯虚拟)界面,描述您希望动物拥有的功能。然后,您可以利用动态调度来调用该功能,甚至不需要知道对象的动态类型。如果需要,您可以始终在子类中创建专用的非虚拟帮助器函数。

还要注意,您需要在容器中存储Animalsby(智能)指针,而不是按值存储。如果按值存储它们,则在插入列表时,它们都将被切片,从而丢失动态类型信息。

正如@Marcin所指出的,如果您确实需要在特定的子类上调用特定的方法,那么使用访问者模式进行双重调度可能是一种更好的方法。

根据具体的代码typeid返回不同的内容。此外,name()可以返回任何内容(包括使第一个字母大写或删除*),仅用于调试。现在我有几个不同的可能答案typeid(animal).name()可以返回什么。

版本1animal是一个类名:

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;
}

版本2 animalAnimal:的typedef

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是一个指针:

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;
}

版本4 animal是一个对象:

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;
}

版本6 animal是对非多态对象的引用:

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;
}

版本7 animal是对多态对象的引用:

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;
}

正如其他人所写的那样,最好不要依赖name()。但是,如果没有一些代码,就不容易说出什么是正确的。

由于列表可以包含任何类型的动物,我假设它是一个指针列表。在这种情况下,如果您将取消引用的指针传递给typeid,那么typeid将考虑对象的最派生类型。

typeid(*animal).name();

就是你想要的。

在每个子类中实现一个函数name()。

如果不使用特殊技巧向基类提供有关派生类型的信息,它就无法知道实例的子类型。最简单的方法是,正如@Joachim Wuttke所建议的,创建一个虚拟函数,强制派生类实现name()方法。

然而,如果你想更喜欢,奇怪地重复出现的模板部分CRTP提供了一个更优雅、更深奥的解决方案:

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

结果(g++):

3Cat
3Dog

结果(vs2008):

class Cat
class Dog

请注意,正如其他人所说,typeid的名称篡改是依赖于平台/编译器的,因此要从上面的名称返回到类,您必须实现依赖于平台或编译器的去映射例程。不是特别困难,但它确实削弱了解决方案的优雅性。