Пошук типу об’єкта в C ++


147

У мене є клас A та інший клас, який успадковує його, B. Я переосмислюю функцію, яка приймає об'єкт типу A як параметр, тому я повинен приймати А. Однак пізніше я називаю функції, які має лише B, тому я хочу повернути помилкове значення, а не продовжувати, якщо переданий об'єкт не типу B.

Який найкращий спосіб дізнатися, який тип об'єкта передається моїй функції?

Відповіді:


162

динамичний_кас повинен зробити свою справу

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

dynamic_castКлючове слово кидає геодезичне від одного покажчика або посилального типу до іншого, виконуючи перевірку виконання для забезпечення достовірності гіпсі.

Якщо ви спробуєте передати покажчик на тип, який не є типом фактичного об'єкта, результатом виступу буде NULL. Якщо ви спробуєте привласнити посилання на тип, який не є типом фактичного об'єкта, команда буде видавати bad_castвиняток.

Переконайтесь, що в базовому класі є принаймні одна віртуальна функція, щоб зробити так, щоб динамічна робота працювала.

Wikipedia тема часу виконання інформації про типі

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


1
Що ви маєте на увазі, у класі Base повинна бути віртуальна функція, щоб змусити роботу динамічної передачі. Це мені здається важливим, що я просто здогадаюся.
GiCo

3
Гаразд знайшов це: Інформація про тип запуску (RTTI) доступна лише для класів, які є поліморфними, це означає, що вони мають принаймні один віртуальний метод. Динамічний_cast та typeid потребують RTTI.
GiCo

Не dynamic_castкидає, якщо його не конвертоване? Чи є спосіб це зробити без створення кидка?
jww

A* aptr = dynamic_cast<A*>(ptr);// Хіба це не повинно бути таким
Мехді Карамослі

Чи працює це для ПОД, які були передані на 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 Так. це частина заголовків std
Amey Jah

8
Я не бачу як. Імена типів ідентифікаторів не повинні бути корисними та визначені реалізацією.
взуття

3
Найцікавіше тут: Назви екземплярів одного класу не повинні бути рівними. Однак сам TypeId повинен порівнювати рівне для примірників одного і того ж класу, см stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo

1
Примітка gcc повертає назву magled, наприклад 11MyClass. Для розблокування ви можете використовувати бібліотеку розширень ABI в cxxabi.h. Це дає вам abi::__cxa_demangleназву справжнього імені
David G

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

Можливо, вбудуйте у свої об’єкти ідентифікаційний "тег" і використовуйте його для розмежування об'єктів класу A та об'єктів класу B.

Це, однак, показує недолік у дизайні. В ідеалі ті методи в B, яких немає, повинні бути частиною A, але залишити порожніми, і B перезаписувати їх. Це скасовує специфічний для класу код і більше відповідає духу OOP.



4

Тому що ваш клас не є поліморфним. Спробуйте:

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

Зараз BaseClasце поліморфно. Я змінив клас на структура, оскільки члени структури за замовчуванням є загальнодоступними.


3

Ваш опис трохи заплутано.

Взагалі кажучи, хоча деякі C ++ реалізації мають механізми для цього, ви не повинні запитувати про тип. Натомість ви повинні зробити динамічну передачу вказівника на A. Що це буде робити, це те, що під час виконання буде перевірено фактичний вміст вказівника на A. Якщо у вас B, ви отримаєте вказівник на B. Інакше ви отримаєте виняток або нуль.


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

Так, я мав би це уточнити краще. Дякую. Мені б хотілося, щоб слово було описано у типах С, які є посиланням, а не побічним значенням.
Урі

3

Як зазначали інші, ви можете використовувати динамический_каст. Але, як правило, використання динамичного_cast для з'ясування типу похідного класу, над яким ви працюєте, вказує на поганий дизайн. Якщо ви переосмислюєте функцію, яка приймає вказівник на A як параметр, вона повинна мати можливість працювати з методами / даними класу A і не повинна залежати від даних класу B. У вашому випадку замість перевизначення, якщо ви впевнені, що метод, який ви пишете, буде працювати лише з класом B, тоді ви повинні написати новий метод у класі B.


1

Використовуйте перевантажені функції. Не потрібна підтримка динамічної передачі або навіть 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 з буквою A, жодних матеріалів 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.