Нахождение типа объекта в C ++


147

У меня есть класс A и другой класс, который наследуется от него, B. Я переопределяю функцию, которая принимает объект типа A в качестве параметра, поэтому я должен принять A. Однако позже я вызываю функции, которые имеет только B, поэтому я хочу вернуть false и не продолжать, если переданный объект не относится к типу B.

Каков наилучший способ узнать, какой тип объекта передается моей функции?

Ответы:


162

dynamic_cast должен сделать свое дело

TYPE& dynamic_cast<TYPE&> (object);
TYPE* dynamic_cast<TYPE*> (object);

dynamic_castКлючевое слово бросает геодезическое от одного указателя или ссылочного типа к другому, выполняя проверку выполнения для обеспечения достоверности гипсе.

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

Убедитесь, что в Базовом классе есть хотя бы одна виртуальная функция для работы dynamic_cast.

Википедия тема Информация о типе времени выполнения

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


1
Что вы имеете в виду, что в базовом классе должна быть виртуальная функция, чтобы заставить dynamic_cast работать. Это кажется мне важным, что я просто угадаю.
GiCo

3
OK нашел его: Информация о типе времени выполнения (RTTI) доступна только для полиморфных классов, что означает, что у них есть хотя бы один виртуальный метод. dynamic_cast и typeid нуждаются в RTTI.
GiCo

Не dynamic_castбросает, если это не конвертируемый? Есть ли способ сделать это без генерации броска?
jww

A* aptr = dynamic_cast<A*>(ptr);// разве не должно быть так
Мехди Карамосли

Это работает для POD, которые были брошены в uint8_t*? То есть я могу это проверить uint32_t* x = dynamic_cast<uint32_t*>(p), где pнаходится uint8_t*? (Я пытаюсь найти тест на наказание нарушений).
jww

157

Динамическое приведение является лучшим для вашего описания проблемы, но я просто хочу добавить, что вы можете найти тип класса с помощью:

#include <typeinfo>

...
string s = typeid(YourClass).name()

4
Хорошо, если ты действительно не знаешь, какой у тебя объект. Принятый ответ предполагает, что вы делаете.
unludo

4
@xus Да. это часть стандартных заголовков
Amey Jah

8
Я не вижу как. Имена идентификаторов типов не обязаны быть полезными и определяются реализацией.
Чистка

3
Здесь самое интересное: имена экземпляров одного и того же класса не обязательно должны быть одинаковыми. Однако сам TypeId должен сравнивать равное для экземпляров одного и того же класса, см stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo

1
Примечание gcc возвращает помеченное имя, например 11MyClass. Чтобы разобраться, вы можете использовать библиотеку расширений ABI в cxxabi.h. Это дает вам, abi::__cxa_demangleкоторый даст вам настоящее имя
Дэвид Дж

27

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


4
Правда. К сожалению, я работаю над существующим проектом, поэтому я не могу изменить дизайн или что-то в классе А.
lemnisca

11

Просто чтобы закончить, я соберу сборку из Robocide и укажу, что typeidее можно использовать отдельно, без использования name ():

#include <typeinfo>
#include <iostream>

using namespace std;

class A {
public:
    virtual ~A() = default; // We're not polymorphic unless we
                            // have a virtual function.
};
class B : public A { } ;
class C : public A { } ;

int
main(int argc, char* argv[])
{
    B b;
    A& a = b;

    cout << "a is B: " << boolalpha << (typeid(a) == typeid(B)) << endl;
    cout << "a is C: " << boolalpha << (typeid(a) == typeid(C)) << endl;
    cout << "b is B: " << boolalpha << (typeid(b) == typeid(B)) << endl;
    cout << "b is A: " << boolalpha << (typeid(b) == typeid(A)) << endl;
    cout << "b is C: " << boolalpha << (typeid(b) == typeid(C)) << endl;
}

Вывод:

a is B: true
a is C: false
b is B: true
b is A: false
b is C: false

9

Вероятно, встраивайте в ваши объекты идентификатор «тег» и используйте его, чтобы различать объекты класса А и объекты класса В.

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



4

Потому что ваш класс не полиморфный. Пытаться:

struct BaseClas { int base; virtual ~BaseClas(){} };
class Derived1 : public BaseClas { int derived1; };

Сейчас BaseClasполиморфный. Я изменил класс на struct, потому что члены структуры по умолчанию являются публичными.


3

Ваше описание немного сбивает с толку.

Вообще говоря, хотя некоторые реализации C ++ имеют механизмы для этого, вы не должны спрашивать о типе. Вместо этого вы должны сделать dynamic_cast для указателя на A. Что это будет делать, так это то, что во время выполнения будет проверяться фактическое содержимое указателя на A. Если у вас есть B, вы получите указатель на B. В противном случае вы получите исключение или ноль.


1
Следует отметить, что вы получите исключение только в том случае, если вы выполните приведение ссылки, которое завершится неудачно то есть dynamic_cast <T &> (t). Сбой приведения указателя возвращает NULL. т.е. dynamic_cast <T *> (t)
AlfaZulu

Да, я должен был уточнить это лучше. Спасибо. Хотелось бы, чтобы в типах Си было слово description, которое является косвенным, а не побочным.
Ури

3

Как указали другие, вы можете использовать dynamic_cast. Но в целом использование dynamic_cast для определения типа производного класса, с которым вы работаете, указывает на плохой дизайн. Если вы переопределяете функцию, которая принимает указатель A в качестве параметра, тогда она должна иметь возможность работать с методами / данными самого класса A и не должна зависеть от данных класса B. В вашем случае вместо переопределения, если вы уверены, что метод, который вы пишете, будет работать только с классом B, тогда вам следует написать новый метод в классе B.


1

Используйте перегруженные функции. Не требует dynamic_cast или даже RTTI поддержки:

class A {};
class B : public A {};

class Foo {
public:
    void Bar(A& a) {
        // do something
    }
    void Bar(B& b) {
        Bar(static_cast<A&>(b));
        // do B specific stuff
    }
};

Справа от первоначального вопроса: «Позже я вызываю функции, которые есть только у B» - как перегрузка будет работать в таком случае?
Марцин Гил

Когда вы звоните в Бар с буквой А, ничего не происходит. Когда вы вызываете Bar с B, можно вызывать методы, которые существуют только на B. Вы читаете оригинальный вопрос? Бар - это «Я переопределяю функцию, которая принимает объект типа A в качестве параметра»
jmucchiello

7
Это не работает с динамическим полиморфизмом, который, я подозреваю, использует спрашивающий. C ++ не может выбрать перегрузку на основе класса времени выполнения параметра, только на основе типа времени компиляции.
Стив Джессоп

1

Если вы можете получить доступ к библиотеке надстроек , возможно, вам нужна функция type_id_with_cvr () , которая может предоставить тип данных без удаления модификаторов const, volatile, & и & && . Вот простой пример в C ++ 11:

#include <iostream>
#include <boost/type_index.hpp>

int a;
int& ff() 
{
    return a;
}

int main() {
    ff() = 10;
    using boost::typeindex::type_id_with_cvr;
    std::cout << type_id_with_cvr<int&>().pretty_name() << std::endl;
    std::cout << type_id_with_cvr<decltype(ff())>().pretty_name() << std::endl;
    std::cout << typeid(ff()).name() << std::endl;
}

Надеюсь, это полезно.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.