Знать класс подкласса в С++

Я не делал С++, по крайней мере, 7 лет, и внезапно опустился в С++. Мне бы хотелось, чтобы некоторые рекомендации были следующими:

У меня есть класс под названием Animal, и у меня есть 3 класса, которые унаследованы от Animal: Cat, Dog and Bird. Я создал объект списка и использую его для хранения типа Animal.

Этот список может содержать собак кошек и птиц, когда я повторяю этот список животных, я хотел бы знать ближайший тип каждого животного (будь то кошка, собака или птица).

Когда я говорю typeid(animal).name();, он дает мне Animal, что верно, но я хотел бы знать, что такое Animal.

Любые идеи? Должен ли я использовать перечисления?

Ответ 1

Ты почти наверняка не хочешь знать. То, что вы должны сделать, объявить как виртуальные соответствующие методы для взаимодействия с этими животными.

Если вам нужно оперировать их специально, вы можете использовать шаблон посетителя для передачи объекта посетителя или использовать правильные данные в каждом конкретном классе. Если вы настаиваете на наличии тегов (и я подчеркиваю, что это третий вариант - другие два решения оставят ваш код намного чище), у вас есть виртуальный метод под названием classname, который возвращает идентификатор типа (будь то строка или int или без разницы).

Обратите внимание также на точку резки, если у вас есть массив типа объекта, в отличие от типа указателя. Если вы не использовали С++ через 7 лет, вы можете не знать о расширении использования шаблонов, чтобы сделать язык намного лучше. Просмотрите библиотеки, например boost, чтобы узнать, что можно сделать, и как шаблон позволяет вам писать генерируемый тип генерируемого кода.

Ответ 2

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

Ответ 3

Необходимость знать конкретный тип конкретного объекта обычно является запахом дизайна на С++, поэтому я собираюсь предположить, что вы не пытаетесь это сделать.

Вместо этого создайте абстрактный (чистый виртуальный) интерфейс в Animal, который описывает функциональность, которую вы хотите иметь у ваших животных. Затем вы можете использовать динамическую отправку для вызова этой функции, даже не требуя знать динамический тип объекта. При необходимости вы всегда можете создавать частные не виртуальные вспомогательные функции в дочерних классах.

Также обратите внимание, что вам нужно сохранить указатель Animal по (умному) в контейнере, а не по значению. Если вы сохраните их по значению, все они будут нарезаны при вставке в список, потеряв информацию о динамическом типе.

И как отметил @Marcin, использование шаблона посетителя для двойной отправки может быть лучшим подходом, если вам действительно нужно вызывать определенные методы для определенных дочерних классов.

Ответ 4

В зависимости от конкретного кода 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 - это typedef to 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;
}

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(). Но без какого-либо кода нелегко сказать, что правильно.

Ответ 5

Внедрить имя функции() в каждом подклассе.

Ответ 7

Без использования специальных трюков, чтобы предоставить информацию базового класса о производных типах, нет способа узнать, какой подтип является экземпляром. Самый простой способ сделать это, как предлагает @Joachim Wuttke, создать виртуальную функцию, которая вынуждает производные классы внедрять метод name().

Однако, если вы хотите немного поучаствовать, любопытно повторяющийся шаблон parttern 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;

}

result (g++):

3Cat
3Dog

result (vs2008):

class Cat
class Dog

Обратите внимание, что, как утверждают другие, mangling типа typeid зависит от платформы/компилятора, поэтому, чтобы перейти от имен выше к классу, вам нужно реализовать подпрограмму demandling, зависящую от платформы/компилятора. Не особенно сложно, но это отвлекает от элегантности решения.