Argumentem operatora typeid (z nagłówka typeinfo) może być nazwa typu lub dowolne wyrażenie o określonej wartości. Operator zwraca identyfikator typu argumentu, który jest obiektem klasy type_info. Klasa ta, poprzez przeciążenie operatorów ' ==' i ' !=' zapewnia możliwość porównywania obiektów reprezentujących typy, na przykład:
#include <typeinfo> // ... double x = 1.5; // ... if (typeid(x) == typeid(double)) ... // true if (typeid(x) == typeid(36.0)) ... // true if (typeid(x) == typeid(3)) ... // false if (typeid(x) != typeid(3)) ... // true if (typeid(x) != typeid(int)) ... // trueKlasa type_info posiada metodę name, która zwraca C-napis zawierający nazwę typu: nazwy te nie muszą pokrywać się ze standardowymi nazwami typów, choć mogą. Na przykład
typeid(int).name()zwraca nazwę ' int' w środowisku VC++, ale g++ i Intelowski icpc dają po prostu nazwę ' i'. Zwykle nazwy nie są nam do niczego potrzebne, ważne jest tylko porównywanie typów.
Ciekawsze i bardziej pożyteczne jest rozpoznawanie typów w warunkach
dziedziczenia. Szczegóły rozpatrzmy na przykładzie następującego
programu:
1. #include <iostream> 2. #include <typeinfo> 3. using namespace std; 4. 5. struct Pojazd { }; 6. struct Samochod : Pojazd { }; 7. 8. struct Budynek { 9. virtual ~Budynek() { } 10. }; 11. struct Stacja : Budynek { }; 12. 13. 14. 15. int main() { 16. Pojazd poj; 17. Samochod sam1,sam2; 18. Samochod* p_sam1 = &sam1; 19. Pojazd* p_sam2 = &sam2; 20. Pojazd& r_sam1 = sam1; 21. cout << " poj: " << typeid(poj).name() << endl 22. << " sam1: " << typeid(sam1).name() << endl 23. << " sam2: " << typeid(sam2).name() << endl 24. << " p_sam1: " << typeid(p_sam1).name() << endl 25. << " p_sam2: " << typeid(p_sam2).name() << endl 26. << "*p_sam1: " << typeid(*p_sam1).name() << endl 27. << "*p_sam2: " << typeid(*p_sam2).name() << endl 28. << " r_sam1: " << typeid(r_sam1).name() << endl; 29. 30. cout << "Typy *p_sam1 i *p_sam2 sa " 31. << (typeid(*p_sam1) == typeid(*p_sam2) ? 32. "takie same\n" : "rozne\n") << endl; 33. 34. Budynek bud; 35. Stacja sta1,sta2; 36. Stacja* p_sta1 = &sta1; 37. Budynek* p_sta2 = &sta2; 38. Budynek& r_sta1 = sta1; 39. cout << " bud: " << typeid(bud).name() << endl 40. << " sta1: " << typeid(sta1).name() << endl 41. << " sta2: " << typeid(sta2).name() << endl 42. << " p_sta1: " << typeid(p_sta1).name() << endl 43. << " p_sta2: " << typeid(p_sta2).name() << endl 44. << "*p_sta1: " << typeid(*p_sta1).name() << endl 45. << "*p_sta2: " << typeid(*p_sta2).name() << endl 46. << " r_sta1: " << typeid(r_sta1).name() << endl; 47. 48. cout << "Typy *p_sta1 i *p_sta2 sa " 49. << (typeid(*p_sta1) == typeid(*p_sta2) ? 50. "takie same\n" : "rozne\n"); 51. }
poj: 6Pojazd ( 1) sam1: 8Samochod ( 2) sam2: 8Samochod ( 3) p_sam1: P8Samochod ( 4) p_sam2: P6Pojazd ( 5) *p_sam1: 8Samochod ( 6) *p_sam2: 6Pojazd ( 7) r_sam1: 6Pojazd ( 8) Typy *p_sam1 i *p_sam2 sa rozne ( 9) bud: 7Budynek (11) sta1: 6Stacja (12) sta2: 6Stacja (13) p_sta1: P6Stacja (14) p_sta2: P7Budynek (15) *p_sta1: 6Stacja (16) *p_sta2: 6Stacja (17) r_sta1: 6Stacja (18) Typy *p_sta1 i *p_sta2 sa takie same (19)Linie 1-3 i 11-13 wydruku nie wymagają komentarza: typ jest dokładnie taki, jaki jest typ obiektu (wiodące znaki są dodawane do nazw typów przez kompilator; nie musimy się nimi przejmować — inny kompilator może wewnętrznie używać innych nazw typów). Ponieważ argumentami operatora typeid są tu obiekty, do których odnosimy się przez ich nazwę, a nie poprzez wskaźnik lub referencję, żadnego polimorfizmu tak czy owak nie ma.
Typem drukowanym w liniach 4-5 (oraz 14-15) wydruku jest typ wskaźnika, a nie wskazywanego przez ten wskaźnik obiektu (nazwa 'P8Samochod' to nazwa typu wskaźnik do 8Samochod; 'P' od pointer). Porównując wydruk z linii 4 i 5 oraz z linii 14 i 15 widzimy, że w tym przypadku polimorfizm też nie ma nic do rzeczy: typem jest prawdziwy, zadeklarowany typ wskaźnika.
Inaczej jest, kiedy pytamy bezpośrednio o typ obiektu wskazywanego przez wskaźnik, a więc o typ wartości wyrażenia *p, gdzie p jest wskaźnikiem. Ponieważ do obiektu odnosimy się teraz przez wskaźnik, polimorfizm może zadziałać, pod warunkiem, że mamy do czynienia z klasami polimorficznymi, a więc zadeklarowana w nich jest choć jedna metoda wirtualna; może nią być sam destruktor, jak to jest w naszym przykładzie dla pary klas Budynek ← Stacja.
Spójrzmy na linie 6 i 7 wydruku. Prawdziwym typem obiektów wskazywanych zarówno przez wskaźnik p_sam1 jak i p_sam2 jest typ pochodny Samochod. Jednak typem wskaźnika w pierwszym przypadku jest Samochod*, a w drugim Pojazd*. Ponieważ te klasy nie są polimorficzne, typ obiektów wskazywanych *p_sam1 i *p_sam2 zostanie rozpoznany według deklaracji wskaźników, a więc statycznie. Tak więc znalezionym typem wartości wyrażeń *p_sam1 i *p_sam2 będzie odpowiednio Samochod i Pojazd. Inaczej jest dla obiektów będących wartościami wyrażeń *p_sta1 i *p_sta2. Prawdziwy ich typ to typ pochodny Stacja. Odnosimy się do tych obiektów przez wskaźnik, a klasy są polimorficzne. Zatem tym razem rozpoznany zostanie w obu wypadkach prawdziwy typ wskazywanych obiektów — patrz linie 16 i 17.
Rozpoznawanie prawdziwych typów dla klas polimorficznych zachodzi, jak wiemy, również wtedy, gdy do obiektów odnosimy się poprzez referencję. Przykład mamy w liniach 8 i 18: bez polimorfizmu rozpoznany został typ statyczny Pojazd, według zadeklarowanego typu referencji r_sam1, a nie prawdziwy typ obiektu, do którego ta referencja się odnosi, czyli Samochod (linia 8). Natomiast dla klas polimorficznych rozpoznany został prawdziwy typ obiektu (linia 18).
Linie 9 i 19 wskazują jeszcze raz, że porównanie typów obiektów wskazywanych przez wskaźniki lub referencje może zawieść dla klas, które polimorficzne nie są. Typy obiektów *p_sam1 i *p_sam2 zostały rozpoznane jako różne (linia 9), choć tak naprawdę są takie same, tylko typy wskaźników wskazujących na te obiekty są różne. Dla klas polimorficznych typy *p_sta1 i *p_sta2 zostały prawidłowo rozpoznane jako takie same (linia 19), mimo że typy wskaźników były różne.
T.R. Werner, 21 lutego 2016; 20:17