Вопрос идентификации класса C++

Я приведу это в виде примера, чтобы было понятнее.

Скажем, у меня есть вектор животных, и я хочу пройтись по массиву и посмотреть, являются ли элементы собаками или кошками?

class Dog: public Animal{/*...*/};
class Cat: public Animal{/*...*/};

int main()
{
vector<Animal*> stuff;
//cramming the dogs and cats in...

for(/*all elements in stuff*/)
//Something to the effect of:  if (stuff[i].getClass()==Dog) {/*do something*/}

}

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

Есть ли способ сделать это? Заранее спасибо.

Вы не можете этого сделать, это должен быть вектор или Animal *, а не Animal.

Evan Teran 02.12.2008 07:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
3 081
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Вы можете использовать dynamic_cast, если вектор содержит указатели Animal.

vector <Animal *> stuff;

for(int i=0;i<stuff.size();i++) {
    Dog *pDog = dynamic_cast <Dog *> (stuff[i]);
    if (pDog) {
        // do whatever with the dog
    }

    Cat *pCat = dynamic_cast <Cat *> (stuff[i]);
    if (pCat) {
        // and so on
    }
}

но вы должны знать, что это, как правило, не лучшая практика. Вы должны попытаться работать с полиморфизмом, а не против него. Другими словами, попробуйте написать виртуальную функцию Animal, которую Dog и Cat переопределяют, и пусть компилятор автоматически вызовет нужную функцию.

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

Одно из потенциальных преимуществ динамического приведения в производительности состоит в том, что он позволяет «делать что угодно» для выполнения невиртуальных вызовов, которые могут быть встроены. Дело не в том, что vcall сам по себе медленный, а в том, что он не будет встроен. Но вам нужно будет измерить, чтобы узнать, что было быстрее в данном случае.

Steve Jessop 02.12.2008 16:58

Вы уверены, что хотите это сделать? То, что вы собираетесь сделать, прямо противоположно полиморфизму, а полиморфизм - лучшее, что есть в объектно-ориентированном программировании.

Грубо говоря: ничего не делайте если вы животное собака; пусть иерархия Животных знает, что делать, если одним из ее объектов является Собака! :)

Для этого можно использовать оператор typeid, например

if (typeid(stuff[i].getClass())==typeid(Dog))

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

Ответ принят как подходящий

Как отмечали другие, вам не следует использовать ни typeid, ни оператор dynamic_cast, чтобы получить динамический тип того, на что указывает ваш указатель. виртуальные функции были созданы, чтобы избежать такого рода неприятностей.

В любом случае, вот что вы делаете, если вы В самом деле хотите это сделать (обратите внимание, что разыменование итератора даст вам Animal*. Так что, если вы сделаете **it, вы получите Animal&):

for(std::vector<Animal*>::iterator it = v.begin(); it != v.end(); ++it) {
    if (typeid(**it) == typeid(Dog)) {
        // it's a dog
    } else if (typeid(**it) == typeid(Cat)) {
        // it's a cat
    }
}

Обратите внимание, что вы можете применить оператор typeid и к самим типам, как показано выше. Для этого не нужно создавать объект. Также обратите внимание, что метод typeid не работает, если вы передадите ему указатель, например typeid(*it). Используя его таким образом, вы получите только typeid(Animal*), который бесполезен.

Аналогично, dynamic_cast можно использовать:

for(std::vector<Animal*>::iterator it = v.begin(); it != v.end(); ++it) {
    if (Dog * dog = dynamic_cast<Dog*>(*it)) {
        // it's a dog (or inherited from it). use the pointer
    } else if (Cat * cat = dynamic_cast<Cat*>(*it)) {
        // it's a cat (or inherited from it). use the pointer. 
    }
}

Обратите внимание, что в обоих случаях ваш тип Animal должен быть полиморфным. Это означает, что он должен иметь или унаследовать хотя бы одну виртуальную функцию.

Большое спасибо, очень информативно. У меня есть несколько динамических преобразований некоторых других мест в моей программе, но я просто превратил их в виртуальную функцию. Это было в области оценки обнаружения столкновений, и я не могу позволить себе снижение производительности. Я все еще буду использовать typeid, как в вашем примере, потому что это постоянное время

Chad 02.12.2008 08:03

Если вам действительно нужен уровень приложения для идентификации собак и не-собак, вам следует избегать использования RTTI (dynamic_cast и typeid) и сделать это знание явным в иерархии классов.

for (size_t i = 0; i != v.size(); ++i) {
    if (v[i]->isDog()) { v->cleanupPoop(); }
}

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

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

Базовому классу не нужно знать свои производные классы для функционирования иерархии. Я полностью согласен с тем, что needsCleanup лучше, если это возможно, но методы isDog, isCat в Animal - это просто домашняя, низшая версия RTTI. Если вы не можете сделать лучше, вы также можете использовать настоящую вещь IMO.

Steve Jessop 02.12.2008 17:12

Он уступает только в том смысле, что он избыточен для RTTI, поддерживаемого компилятором. Если различие требуется с уровня «бизнес-логики», все же несколько более полезно предоставить своего рода «исполняемую документацию» для программистов обслуживания (вместо того, чтобы заставлять их «разбираться»).

Tom 03.12.2008 04:23

с использованием виртуальных функций:

Как указывали другие ответы, использования виртуальных функций часто бывает достаточно, и это образ мышления "C++". Вот пример использования виртуальных функций:

#include<iostream>
#include<vector>
using namespace std;

/////////////

class Animal {
  public:
    virtual void move() { cout << "animal just moved" << endl; }
};
class Dog : public Animal {
  public:
    void move() { cout << "dog just moved" << endl; }
};
class Cat : public Animal {
  public:
    void move() { cout << "cat just moved" << endl; }
};

void doSomethingWithAnimal(Animal *a) {
  a->move();
}

/////////////

int main() {
  vector<Animal*> vec;
  vector<Animal*>::iterator it;

  Animal *a = new Animal;
  Dog *d = new Dog;
  Cat *c = new Cat;

  vec.push_back(a);
  vec.push_back(d);
  vec.push_back(c);

  it = vec.begin();

  while( it != vec.end() ) {
    doSomethingWithAnimal(*it);

    it++;
  }

  return 0;
}

Если этого будет недостаточно, то другие уже опубликовали ответы, которые фактически используют условную логику вместо полимеризованной логики.

Принятый ответ правильный, но вы должны знать, что есть еще один вариант, о котором не упоминалось. У вас может быть виртуальная функция в классе Animal с именем «type ()», которая может возвращать int или строку (или любой сопоставимый тип).

Так например:

class Animal {
    /*...*/
public:
    virtual std::string type() const { return "animal"; }
};

class Dog: public Animal{
    /*...*/
public:
    virtual std::string type() const { return "dog"; }
};

class Cat: public Animal{
    /*...*/
public:
    virtual std::string type() const { return "cat"; }
};

Таким образом, вы могли просто сделать:

if (array[i]->type() == "dog") { }

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

Просто еще один вариант.

Полезно, когда ваш компилятор не может выполнять RTTI или ваша система настолько ограничена, что вы отключили его вообще и хотите, чтобы он использовался только для одной небольшой части иерархии. Если ваша иерархия достаточно мала, вы можете использовать int (битовый набор), позволяющий быстро проверить наследование с помощью маски и сравнить.

Steve Jessop 02.12.2008 17:22

Другие вопросы по теме