Znajdowanie typu obiektu w C ++

147

Mam klasę A i inną klasę, która dziedziczy po niej B. Zastępuję funkcję, która przyjmuje obiekt typu A jako parametr, więc muszę zaakceptować A. Jednak później wywołuję funkcje, które ma tylko B, więc chcę zwrócić false i nie kontynuować, jeśli przekazany obiekt nie jest typu B.

Jaki jest najlepszy sposób, aby dowiedzieć się, jakiego typu jest obiekt przekazany do mojej funkcji?

lemnisca
źródło

Odpowiedzi:

162

dynamic_cast powinien załatwić sprawę

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

Plik dynamic_castKluczowe rzuca punkt odniesienia z jednego wskaźnika lub odniesienia do innego typu, przeprowadza kontrolę wykonania w celu zapewnienia ważności obsady.

Jeśli spróbujesz rzutować na wskaźnik do typu, który nie jest typem rzeczywistego obiektu, wynik rzutowania będzie miał wartość NULL. Jeśli spróbujesz rzutować odwołanie do typu, który nie jest typem rzeczywistego obiektu, rzutowanie zgłosi bad_castwyjątek.

Upewnij się, że w klasie bazowej jest co najmniej jedna funkcja wirtualna, aby funkcja dynamic_cast działała.

Temat Wikipedii Informacje o typie czasu wykonywania

RTTI jest dostępne tylko dla klas, które są polimorficzne, co oznacza, że ​​mają co najmniej jedną metodę wirtualną. W praktyce nie stanowi to ograniczenia, ponieważ klasy bazowe muszą mieć wirtualny destruktor, aby umożliwić obiektom klas pochodnych prawidłowe czyszczenie, jeśli zostaną usunięte ze wskaźnika podstawowego.

yesraaj
źródło
1
Co masz na myśli mówiąc, że w klasie Base musi istnieć funkcja wirtualna, aby funkcja dynamic_cast działała. Wydaje mi się to ważne, że po prostu zgadnę.
GiCo
3
Znalazłem OK: Informacje o typie czasu wykonywania (RTTI) są dostępne tylko dla klas, które są polimorficzne, co oznacza, że ​​mają co najmniej jedną metodę wirtualną. dynamic_cast i typeid wymagają RTTI.
GiCo
Nie dynamic_castrzuca się, jeśli nie jest wymienny? Czy można to zrobić bez generowania rzutu?
jww
A* aptr = dynamic_cast<A*>(ptr);// Czy nie powinno tak być
Mehdi Karamosly
Czy to działa w przypadku POD, które zostały przesłane do pliku uint8_t*? To znaczy, czy mogę to sprawdzić uint32_t* x = dynamic_cast<uint32_t*>(p), gdzie pjest uint8_t*? (Próbuję znaleźć test na wykroczenia).
jww
157

Rzutowanie dynamiczne jest najlepsze dla twojego opisu problemu, ale chcę tylko dodać, że możesz znaleźć typ klasy za pomocą:

#include <typeinfo>

...
string s = typeid(YourClass).name()
Robocide
źródło
4
Dobrze, jeśli naprawdę nie wiesz, jaki jest twój obiekt. Zaakceptowana odpowiedź zakłada, że ​​tak.
unludo
4
@xus Yes. jest to część standardowych nagłówków
Amey Jah,
8
Nie wiem jak. Nazwy identyfikatorów typów nie muszą być przydatne i są zdefiniowane w implementacji.
But
3
Najciekawsze tutaj: nazwy instancji tej samej klasy nie muszą być równe. Jednak sam typ typeid musi być równy dla instancji tej samej klasy, patrz stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo
1
Uwaga gcc zwraca nazwę magled, np 11MyClass. Aby odblokować, możesz użyć biblioteki rozszerzeń ABI w programie cxxabi.h. To daje ci abi::__cxa_demangleprawdziwe imię
David G
27

Nazywa się to RTTI , ale prawie na pewno chcesz ponownie rozważyć swój projekt tutaj, ponieważ znalezienie typu i robienie specjalnych rzeczy na jego podstawie sprawia, że ​​twój kod jest bardziej kruchy.

Ana Betts
źródło
4
Prawdziwe. Niestety pracuję nad istniejącym projektem, więc nie mogę zmienić projektu ani niczego w klasie A.
lemnisca
11

Aby być kompletnym, zbuduję kompilację z Robocide i wskażę, że typeidmożna jej używać samodzielnie bez używania 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;
}

Wynik:

a is B: true
a is C: false
b is B: true
b is A: false
b is C: false
firebush
źródło
9

Prawdopodobnie umieść w swoich obiektach "znacznik" ID i używaj go do rozróżniania obiektów klasy A i obiektów klasy B.

To jednak pokazuje wadę w projekcie. Idealnie byłoby, gdyby te metody w B, których A nie ma, powinny być częścią A, ale pozostawione puste, a B je nadpisuje. Eliminuje to kod specyficzny dla klasy i jest bardziej w duchu OOP.

wolna przestrzeń
źródło
8

Szukasz dynamic_cast<B*>(pointer)

Joshua
źródło
4

Ponieważ twoja klasa nie jest polimorficzna. Próbować:

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

Teraz BaseClasjest polimorficzny. Zmieniłem klasę na struct, ponieważ członkowie struktury są domyślnie publiczne.

c64zottel
źródło
3

Twój opis jest trochę zagmatwany.

Ogólnie rzecz biorąc, chociaż niektóre implementacje C ++ mają do tego mechanizmy, nie powinieneś pytać o typ. Zamiast tego powinieneś wykonać dynamic_cast na wskaźniku do A. To spowoduje, że w czasie wykonywania zostanie sprawdzona rzeczywista zawartość wskaźnika do A. Jeśli masz B, otrzymasz wskaźnik do B. W przeciwnym razie otrzymasz wyjątek lub wartość null.

Uri
źródło
1
Należy zauważyć, że otrzymasz wyjątek tylko wtedy, gdy wykonasz rzutowanie referencyjne, które się nie powiedzie. tj. dynamic_cast <T &> (t). Nieudane rzutowanie wskaźnika zwraca wartość NULL. tj. dynamic_cast <T *> (t)
AlfaZulu
Tak, powinienem był to lepiej wyjaśnić. Dzięki. Chciałbym, żeby było słowo opisywane w typach C, które są raczej przez odniesienie niż przez wartość.
Uri
3

Jak wskazali inni, możesz użyć dynamic_cast. Ale generalnie użycie dynamic_cast do znalezienia typu klasy pochodnej, nad którą pracujemy, wskazuje na zły projekt. Jeśli nadpisujesz funkcję, która przyjmuje wskaźnik A jako parametr, powinna być w stanie pracować z metodami / danymi samej klasy A i nie powinna zależeć od danych klasy B. W twoim przypadku zamiast nadpisywania, jeśli jesteś pewien, że metoda, którą piszesz będzie działała tylko z klasą B, wtedy powinieneś napisać nową metodę w klasie B.

Naveen
źródło
1

Użyj przeciążonych funkcji. Nie wymaga obsługi dynamic_cast ani nawet 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
    }
};
jmucchiello
źródło
Od razu od pierwotnego pytania: „Później wywołuję funkcje, które ma tylko B” - jak w takim przypadku zadziałałoby przeciążenie?
Marcin Gil
Kiedy dzwonisz do Bara z A, nic się nie dzieje. Kiedy wywołujesz Bar z B, można wywołać metody, które istnieją tylko na B. Czy przeczytałeś oryginalne pytanie? Bar to jego „
Nadpisuję
7
To nie działa z dynamicznym polimorfizmem, którego, jak podejrzewam, używa pytający. C ++ nie może wybrać przeciążenia na podstawie klasy środowiska uruchomieniowego parametru, tylko na podstawie typu czasu kompilacji.
Steve Jessop
1

Jeśli masz dostęp do biblioteki boost, być może funkcja type_id_with_cvr () jest tym, czego potrzebujesz, która może zapewnić typ danych bez usuwania modyfikatorów const, volatile oraz && . Oto prosty przykład w 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;
}

Mam nadzieję, że to jest przydatne.

Kehe CAI
źródło