我已经至少 7 年没有接触过 C++ 了,突然就陷入了 C++ 项目中。我想要一些以下方面的指导:
我有一个名为 Animal 的类,并且我有 3 个继承自 Animal 的类:Cat、Dog 和 Bird。我创建了一个列表对象并使用它来存储类型 Animal。
这个列表可以包含猫、狗和鸟,当我迭代这个动物列表时,我想知道每个动物的直接类型(无论是猫、狗还是鸟)。
当我说
typeid(animal).name();
时,它给了我动物,这是真的,但我想知道是什么样的动物。
有什么想法吗?我应该使用枚举吗?
你几乎肯定不想知道。您应该做的是将与这些动物互动的虚拟方法声明为适当的方法。
如果您需要专门对它们进行操作,您可以使用访问者模式来传递访问者对象或在每个具体类中使用正确的数据。如果您坚持使用标签(我强调这是第三种选择 - 其他两个解决方案将使您的代码更加清晰),请使用一个名为
classname
的虚拟方法,它返回类型标识符(无论是字符串还是 int 或任何)。
如果您有一个对象类型的数组,而不是指针类型的数组,请注意有关切片的一点。如果您已经 7 年没有使用过 C++,您可能没有意识到模板使用量的增长使该语言变得更好。查看像 boost 这样的库,看看可以做什么,以及模板如何允许您编写类型推断的泛型代码。
Dog* dog = dynamic_cast<Dog*>(myAnimalPointer);
if (dog == NULL)
{
// This animal is not a dog.
}
需要知道特定的具体对象类型通常是 C++ 中的设计味道,所以我建议您不要尝试这样做。
相反,在
Animal
中创建一个抽象(纯虚拟)界面来描述您希望动物拥有的功能。然后,您可以利用动态分派来调用该功能,甚至不需要知道对象的动态类型。如果需要,您始终可以在子类中创建私有非虚拟辅助函数。
另请注意,您需要通过(智能)指针而不是通过值来存储
Animal
。如果您按值存储它们,它们将在插入列表时全部被切片,从而丢失动态类型信息。
正如 @Marcin 指出的,如果您确实需要在特定子类上调用特定方法,则使用访问者模式进行双重调度可能是更好的方法。
由于列表可以包含任何类型的动物,我将假设它是一个指针列表。 在这种情况下,如果您向 typeid 传递取消引用的指针,则 typeid 将考虑对象的最派生类型。
typeid(*animal).name();
就是您正在寻找的。
根据具体代码
typeid
返回不同的东西。另外name()
可以返回任何内容(包括将第一个字母变为大写或删除*),它仅用于调试。现在我有一些不同的可能答案,typeid(animal).name()
可以返回什么。
版本 1
animal
是类名:
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
animal
是 Animal
: 的 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;
}
版本 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()
。但如果没有一些代码,就很难说出什么是正确的。
在每个子类中实现一个函数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
结果(与 2008 年相比):
class Cat
class Dog
请注意,正如其他人所说,typeid 的名称修饰是依赖于平台/编译器的,因此要从上面的名称返回到类,您必须实现依赖于平台/编译器的修饰例程。 并不是特别困难,但它确实削弱了解决方案的优雅性。
当
typeid(animal).name()
返回基类名称而不是实际实例化的类型时,则基类不是多态的(即通常没有虚拟析构函数),这将是解决方案。