Show Lecture.Reflection as a slide show.
CS253 Reflection
Narcissus
Inclusion
To use the class type_info or typeid(), you need to:
#include <typeinfo>
typeid
- The typeid() operator (it looks like a function, but it’s
really an operator) returns an object of type type_info.
type_info.name()
returns a
implementation-defined C++ string representing the type.
- type_info objects can’t be copied, so the code below binds
references to typeid()’s return values.
- You can compare type_info objects for equality,
int alpha;
short beta;
const type_info &ta = typeid(alpha), &tb = typeid(beta);
cout << "type of alpha: " << ta.name() << '\n'
<< "type of beta: " << tb.name() << '\n'
<< "Equal? " << boolalpha << (ta==tb) << '\n';
type of alpha: i
type of beta: s
Equal? false
Easier
Really, one rarely actually instantiates a variable of class
type_info. You usually just call methods on the temporary object
returned by typeid():
float gamma;
cout << "type of gamma: " << typeid(gamma).name();
type of gamma: f
You can also pass a type as an argument to typeid().
cout << "type of double: " << typeid(double).name();
type of double: d
Built-in Types
Every type has a corresponding implementation-defined encoding.
For your amusement:
cout << typeid(bool ).name() << '\n'
<< typeid(char ).name() << '\n'
<< typeid(unsigned char ).name() << '\n'
<< typeid(short ).name() << '\n'
<< typeid(unsigned short ).name() << '\n'
<< typeid(int ).name() << '\n'
<< typeid(unsigned int ).name() << '\n'
<< typeid(long ).name() << '\n'
<< typeid(unsigned long ).name() << '\n'
<< typeid(long long ).name() << '\n'
<< typeid(unsigned long long).name() << '\n'
<< typeid(float ).name() << '\n'
<< typeid(double ).name() << '\n'
<< typeid(long double ).name() << '\n';
b
c
h
s
t
i
j
l
m
x
y
f
d
e
Do not memorize these. They vary between compilers, and could
change without notice.
Composite Types
class Foobar {};
cout << typeid(bool * ).name() << '\n'
<< typeid(const bool *).name() << '\n'
<< typeid(bool ****** ).name() << '\n'
<< typeid(int[3] ).name() << '\n'
<< typeid(int[3][4][5]).name() << '\n'
<< typeid(char *[] ).name() << '\n'
<< typeid(main ).name() << '\n'
<< typeid(Foobar ).name() << '\n';
Pb
PKb
PPPPPPb
A3_i
A3_A4_A5_i
A_Pc
FivE
Z4mainE6Foobar
P
: a pointer
A
length : an array
Z
length : a qualifier
E
length : a class or struct
Name Mangling
- You may recall this mapping of types to strings from way back in the
functions lecture.
- It was used by the compiler to do function overloading.
- The g++ compiler uses the same mapping for
typeinfo::name()
.
This is not required by the standard, but, why have two mappings
when one will do?
Function declaration | Actual name used by g++ |
int sum(int, int) | _Z3sumii |
double sum(int) | _Z3sumi |
const char *sum() | _Z3sumv |
bool sum(double, int, short) | _Z3sumdis |
Polymorphic Example
struct Base { virtual void foo(){} };
struct D1 : Base {};
struct D2 : Base {};
void foo(Base *b) {
if (typeid(*b) == typeid(D1))
cout << "It’s a D1\n";
else if (typeid(*b) == typeid(D2))
cout << "It’s a D2\n";
}
int main() {
D1 d1;
D2 d2;
foo(&d1);
foo(&d2);
}
It’s a D1
It’s a D2
- For RTTI (Run-Time Type Identification), a class must
have at least one virtual function.
- I believe that the implementation is tied into the vtable,
used for resolving virtual functions.
dynamic_cast
struct Base { virtual void foo(){} };
struct D1 : Base {};
struct D2 : Base {};
void foo(Base *b) {
D1 *p = dynamic_cast<D1 *>(b);
cout << (p ? "D1\n" : "D2\n");
}
int main() {
D1 d1;
D2 d2;
foo(&d1);
foo(&d2);
}
D1
D2
dynamic_cast converts a base class pointer to a derived class pointer.
If it fails, you get a null pointer.
Guilt
- Use of typeid() or dynamic_cast indicates that your
class hierarchy is incorrect.
- If you’re asking “Is this pointer such-and-such a type?”,
consider adding a method that answers the real question.
- However, sometimes you’re stuck with an existing class
hierarchy, and so you do what you have to.