RTTI is the abbreviation of "Runtime Type Information", which means runtime type information. It provides a method to determine object type at runtime.
1. typeid function
1 For the built-in data types of c++, typeid can easily output their data types.
#include <iostream> #include <typeinfo> using namespace std; int main() { short s = 2; unsigned ui = 10; int i = 10; char ch = 'a'; wchar_t wch = L'b'; float f = 1.0f; double d = 2; cout<<typeid(s).name()<<endl; // short cout<<typeid(ui).name()<<endl; // unsigned int cout<<typeid(i).name()<<endl; // int cout<<typeid(ch).name()<<endl; // char cout<<typeid(wch).name()<<endl; // wchar_t cout<<typeid(f).name()<<endl; // float cout<<typeid(d).name()<<endl; // double return 0; }
2 For the class objects you create, you can still output their data types
#include <iostream> #include <typeinfo> using namespace std; class A { public: void Print() { cout<<"This is class A."<<endl; } }; class B : public A { public: void Print() { cout<<"This is class B."<<endl; } }; struct C { void Print() { cout<<"This is struct C."<<endl; } }; int main() { A *pA1 = new A(); A a2; cout<<typeid(pA1).name()<<endl; // class A * cout<<typeid(a2).name()<<endl; // class A B *pB1 = new B(); cout<<typeid(pB1).name()<<endl; // class B * C *pC1 = new C(); C c2; cout<<typeid(pC1).name()<<endl; // struct C * cout<<typeid(c2).name()<<endl; // struct C return 0; }
3 RTTI Core
#include <iostream> #include <typeinfo> using namespace std; class A { public: void Print() { cout<<"This is class A."<<endl; } }; class B : public A { public: void Print() { cout<<"This is class B."<<endl; } }; int main() { A *pA = new B(); cout<<typeid(pA).name()<<endl; // class A * cout<<typeid(*pA).name()<<endl; // class A return 0; }
Analysis:
- I used typeid twice, but the parameters are different and the output results are different. When I specify pA, because pA is a pointer of type A, the output is class A*.
- When I specify * pA, it represents the type of object that pA refers to, so the output is class A.
- So we need to distinguish between typeid(*pA) and typeid(pA), which are not the same thing.
However, there is a problem here. Clearly, pA actually points to B. Why does it get class A?
#include <iostream> #include <typeinfo> using namespace std; class A { public: virtual void Print() { cout<<"This is class A."<<endl; } }; class B : public A { public: void Print() { cout<<"This is class B."<<endl; } }; int main() { A *pA = new B(); cout<<typeid(pA).name()<<endl; // class A * cout<<typeid(*pA).name()<<endl; // class B return 0; }
Focus on:
Well, I turned the Print function into a virtual function, and the output was different. What does that mean?
- This is RTTI's trick. When there is no virtual function in a class, typeid is a compile-time thing, that is, a static type, just like cout typeid (* pA). name () endl; output class A.
- When there are virtual functions in a class, typeid is a runtime thing, that is, a dynamic type, just like cout typeid (* pA). name () endl above, and class B output, we often make mistakes in practical programming, we must bear in mind.
(It's really important to see more about a class with virutal and without virtual. It's totally different for a compiler to do, so it's important to see if this class has virtual.)
2. Comparison operators in type_info class
Use type_info class overloaded== and!= to compare the type of the two objects to be equal
This is often used to compare the equality of objects between two classes with virtual functions, such as the following code:
#include <iostream> #include <typeinfo> using namespace std; class A { public: virtual void Print() { cout<<"This is class A."<<endl; } }; class B : public A { public: void Print() { cout<<"This is class B."<<endl; } }; class C : public A { public: void Print() { cout<<"This is class C."<<endl; } }; void Handle(A *a) { if (typeid(*a) == typeid(A)) { cout<<"I am a A truly."<<endl; } else if (typeid(*a) == typeid(B)) { cout<<"I am a B truly."<<endl; } else if (typeid(*a) == typeid(C)) { cout<<"I am a C truly."<<endl; } else { cout<<"I am alone."<<endl; } } int main() { A *pA = new B(); Handle(pA); delete pA; pA = new C(); Handle(pA); return 0; }
This is a usage. I'll summarize how to use dynamic_cast to achieve the same function later.
3. dynamic_cast mechanism
Using dynamic_cast mechanism to implement the above code (dynamic_cast is a very common method)
#include <iostream> #include <typeinfo> using namespace std; class A { public: virtual void Print() { cout<<"This is class A."<<endl; } }; class B { public: virtual void Print() { cout<<"This is class B."<<endl; } }; class C : public A, public B { public: void Print() { cout<<"This is class C."<<endl; } }; int main() { A *pA = new C; //C *pC = pA; // Wrong compiler will prompt errors C *pC = dynamic_cast<C *>(pA); if (pC != NULL) { pC->Print(); } delete pA; }
In the above code, if we assign pA directly to pC, the compiler will prompt errors, and when we add dynamic_case, everything will be ok ay. So what did dynamic_cast do in the back?
dynamic_cast is mainly used in polymorphism, which allows type conversion at runtime, so that programs can safely convert types in a class hierarchy, converting base class pointers (references) to derived class pointers (references).
When a virtual function exists in a class, the compiler adds a vptr pointer to the virtual function table in the member variables of the class. The type_info object associated with each class is also pointed out through the virtual table, which is usually placed in the first slot of the table. When we do dynamic_cast, the compiler checks the grammar for us. If the static type of the pointer is the same as the target type, nothing is done; otherwise, first, the pointer is adjusted so that it points to vftable and passes it and the adjusted pointer, the adjusted offset, the static type and the target type to the internal function. The last parameter indicates whether the pointer or reference is converted. The only difference between the two is that if the conversion fails, the former returns NULL and the latter throws a bad_cast exception. For the example program in the use of the typeid function, I use dynamic_cast to make changes, the code is as follows:
#include <iostream> #include <typeinfo> using namespace std; class A { public: virtual void Print() { cout<<"This is class A."<<endl; } }; class B : public A { public: void Print() { cout<<"This is class B."<<endl; } }; class C : public A { public: void Print() { cout<<"This is class C."<<endl; } }; void Handle(A *a) { if (dynamic_cast<B*>(a)) { cout<<"I am a B truly."<<endl; } else if (dynamic_cast<C*>(a)) { cout<<"I am a C truly."<<endl; } else { cout<<"I am alone."<<endl; } } int main() { A *pA = new B(); Handle(pA); delete pA; pA = new C(); Handle(pA); return 0; }
This is a rewritten version using dynamic_cast. In a real project, this approach will use more points.
Principles of RTTI's underlying implementation:
Simply put, a new entry is added to the virtual function table of a class.
This part has detailed analysis in Chapters 3, 4 and 7 of Deep Search c++ Object Model.