14

我至少有 7 年没有做过 C++ 了,突然间我深深地投入到了一个 C++ 项目中。我想要一些关于以下方面的指导:

我有一个名为 Animal 的类,我有 3 个继承自 Animal 的类:Cat、Dog 和 Bird。我创建了一个列表对象并使用它来存储类型 Animal。

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

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

有任何想法吗??我应该使用枚举吗?

4

7 回答 7

22

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

如果您需要专门对它们进行操作,您可以使用访问者模式来传递访问者对象或在每个具体类中处理正确的数据。如果您坚持使用标签(我强调这是第三种选择 - 其他两种解决方案将使您的代码更清晰),请调用classname一个返回类型标识符的虚拟方法(无论是作为字符串还是 int 或其他)。

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

于 2013-07-16T14:16:31.030 回答
18
Dog* dog = dynamic_cast<Dog*>(myAnimalPointer);
if (dog == NULL)
{ 
    // This animal is not a dog.
}
于 2013-07-16T14:15:06.767 回答
11

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

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

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

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

于 2013-07-16T14:23:31.427 回答
3

根据具体代码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;
}

版本 2animal是一个 typedef 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;
}

版本 3animal是一个指针:

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

版本 4animal是一个对象:

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

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

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

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

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(). 但是如果没有一些代码,就很难说什么是正确的。

于 2013-07-16T15:09:04.857 回答
2

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

typeid(*animal).name();

是你正在寻找的。

于 2013-07-16T14:24:48.817 回答
1

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

于 2013-07-16T14:17:22.060 回答
0

如果不使用特殊技巧为您的基类提供有关派生类型的信息,它就无法知道实例是什么子类型。正如@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 的名称修饰依赖于平台/编译器,因此要从上面的名称返回到类,您必须实现一个依赖于平台/编译器的去修饰例程。不是特别困难,但它确实使解决方案失去了优雅。

于 2014-08-12T15:01:34.763 回答