在C ++中了解子类的类

时间:2013-07-16 14:12:31

标签: c++ oop inheritance polymorphism

我至少在7年内没有完成C ++,而且在C ++项目中我突然陷入了困境。我希望得到以下指导:

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

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

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

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

7 个答案:

答案 0 :(得分:21)

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

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

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

答案 1 :(得分:15)

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

答案 2 :(得分:11)

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

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

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

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

答案 3 :(得分:3)

根据具体代码typeid返回不同的内容。另外name()可以返回任何内容(包括使第一个字母大写或删除*),它仅用于debuging。现在我有一些不同的可能答案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 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是对非多态 objcet的引用:

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()。但是如果没有一些代码,说出什么是正确的并不容易。

答案 4 :(得分:1)

在每个子类中实现函数名称()。

答案 5 :(得分:1)

由于列表可以包含任何类型的动物,我将假设它是一个指针列表。 In such case typeid will consider the most derived type of the object if you pass it the dereferenced pointer.

typeid(*animal).name();

您在寻找什么。

答案 6 :(得分: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的名称是依赖于平台/编译器的,因此要从上面的名称返回到类,必须实现依赖于平台/编译器的demangling例程。并不是特别困难,但它确实消除了解决方案的优雅。