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)) ... // true
Klasa
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