http://www.cnblogs.com/kkdd-2013/p/5601783.html
RTTI—運行時類型識別
RTTI:Run-Time Type Identification。
那么RTTI如何來體現呢?這就要涉及到typeid和dynamic_cast這兩個知識點了。為了更好的去理解,那么我們就通過一個例子來說明。這個例子大家已經非常熟悉了,如下:
首先定義一個Flyable類,在這個類當中有兩個純虛函數:takeoff(起飛)和land(降落)。我們又定義了一個鳥類,并且公有繼承了Flyable類,既然public繼承了Flyable,就要去實現起飛和降落這兩個函數,此外,作為鳥類來說,還有一個自己特有的函數foraging(覓食)。同時,我們還定義了另外一個類Plane,其也以public方式繼承了Flyable,并且也實現了起飛和降落這兩個函數,此外,作為飛機類來說,其還有一個自己特有的函數Carry(運輸)。
在使用的時候,我們假設有如下一個函數dosomething,它的傳入參數是Flyable的一個指針,如下:
在這個函數當中,我們可以使用obj這個指針去調用起飛和降落這兩個函數。同時,我們可以想一想,如果我們能夠對傳入的這個指針再做進一步的判斷,如如說,我們判斷出如果它是一個Bird對象指針,那么我們是不是就可以用這個指針去調用覓食這個函數呢?同理,如果我們判斷出它是一個Plane對象指針,那么我們是不是也就可以用這個指針去調用運輸這個函數呢?如果想要做到這樣的效果,那么就要用到這節課開始提到的知識:運行時類型識別(RTTI)。
我們可以看到,當我們去實現dosomething這個函數的時候,如下:
我們在這調用了起飛函數,最后一行代碼調用了降落函數。我們在調用完起飛這個函數之后,我們通過typeid(*obj).name()這樣的方法就可以將當前的obj這個指針指向的實際的對象類型打印出來了(比如傳入的是飛機,打印出來的就是Plane;如果傳入的是Bird,那么打印出來的就是Bird)。當然,我們可以還可以通過if語句對類型進行比對,如果我們想要判斷當前的obj是不是一個Bird類型,我們就可以通過上面的if判斷語句的方法進行比對,比對完成之后,我們就可以將obj通過dynamic_cast的方式,將其轉換為Bird指針。轉換的時候,需要注意的是,dynamic_cast<Bird *>(obj),尖括號中是目標類型。轉換完之后,我們就可以用bird這個指針去調用覓食這個函數。
總結:
dynamic_cast注意事項:
- l? 只能應用于指針和引用的轉換
- l? 要轉換的類型當中必須包含虛函數(如果沒有虛函數,轉換就會失敗)
- l? 轉換成功返回子類的地址,失敗返回NULL
typeid的注意事項:
- l? type_id返回一個type_info對象的引用
- l? 如果想要通過基類的指針獲得派生類的數據類型,基類必須帶有虛函數
- l? 只能獲取對象的實際類型(也就是說,即便這個類含有虛函數,也只能判斷當前對象是基類還是子類,而沒有辦法判斷當前指針是基類還是子類)
下面我們來看一看type_info中的內容,如下:
對于type_info這個類來說,當中我們用到一個name()函數。我們在之前的例子當中,通過typeid(*obj)獲取到的就是一個type_info的引用,通過這個引用就可以調用name()這個成員函數(typeid(*obj).name()),那么這個被調用的name()成員函數就是在這所看到的name()。語句bool operator == (const type_info& rhs) const;是一個運算符重載(這部分內容后面介紹),大家只要知道,在進行了運算符重載之后,這里的==就可以使得前面的例子中兩個type_info對象的比對了(typeid(*obj) = = typeid(Bird))。
RTTI代碼實踐
/* *************************************************? */
/* RTTI
????? 1. Flyable類,成員函數:takeoff()和land()
??? ? 2. Plane類,成員函數:takeoff()、land()和carry()
??? ? 3. Bird類,成員函數:takeoff()、land()和foraging()
??? ? 4. 全局函數dosomething(Flyable *obj)
*/
/* *************************************************? */
程序結構:
頭文件(Flyable.h)
#ifndef FLYABLE_H #define FLYABLE_H//在Flyable這個類中定義兩個純虛函數takeoff()和land() class Flyable { public:virtual void takeoff() = 0;virtual void land() = 0; };#endif
頭文件(Bird.h)
#ifndef Bird_H #define Bird_H#include "Flyable.h" #include <string> using namespace std;class Bird:public Flyable //公有繼承了Flyable { public:void foraging();//對于Bird類來說,其具有一個特有的成員函數foraging(覓食)virtual void takeoff(); //實現了Flyable中的虛函數takeoff和landvirtual void land(); };#endif
源程序(Bird.cpp)
#include <iostream> #include "Bird.h"using namespace std;void Bird::foraging() {cout << "Bird --> foraging()" << endl; }void Bird::takeoff() {cout << "Bird --> takeoff()" << endl; }void Bird::land() {cout << "Bird --> land()" << endl; }
頭文件(Plane.h)
#ifndef PLANE_H #define PLANE_H#include "Flyable.h" #include <string> using namespace std;class Plane:public Flyable //公有繼承了Flyable { public:void carry(); //Plane具有一個特有的成員函數carry(運輸)virtual void takeoff(); //實現了Flyable中的虛函數takeoff和landvirtual void land();};#endif
源程序(Plane.cpp)
#include <iostream> #include "Plane.h"using namespace std;void Plane::carry() {cout << "Plane --> carry()" << endl; }void Plane::takeoff() {cout << "Plane --> takeoff()" << endl; }void Plane::land() {cout << "Plane --> land()" << endl; }
主調程序(demo.cpp)
#include <iostream> #include "stdlib.h" #include "Bird.h" #include "Plane.h"using namespace std; void dosomething(Flyable *obj) {cout << typeid(*obj).name() << endl; //打印傳入的對象指針究竟是什么類型的對象obj->takeoff();if(typeid(*obj) == typeid(Bird)) //這里判斷obj這個指針所指向的對象是不是Bird類型{Bird *bird = dynamic_cast<Bird *>(obj); //將obj這個指針通過dynamic_cast強制轉換為Bird指針,并且將這個指針賦值給一個新的指針birdbird->foraging(); //通過這個bird指針來調用foraging(覓食)這個成員函數}if(typeid(*obj) == typeid(Plane)) //這里判斷obj這個指針所指向的對象是不是Bird類型{Plane *plane = dynamic_cast<Plane *>(obj); //將obj這個指針通過dynamic_cast強制轉換為Plane指針,并且將這個指針賦值給一個新的指針planeplane->carry(); //通過這個plane指針來調用carry(運輸)這個成員函數}obj->land(); }
我們來到主調函數main()下面,先實例化一個Bird對象b,然后通過調用dosomething函數來傳入Bird這個對象b,由于dosoething這個函數傳入的參數是一個獨享指針,所以這里傳入的應該是對象b的地址(&b),如下:
int main() {Bird b;dosomething(&b);system("pause");return 0; }
我們按一下F5,看一下運行結果:
通過運行的結果來比較相應的程序,看一看是如何來運行的。
首先打印出的第一行是 “class Bird”,其是通過dosomething函數中的cout語句打印出來的;接下來打印出的是“Bird –> takeoff()”,其是通過代碼obj->takeoff();實現的;第三行打印出的是“Bird –> foraging()”,其運行的一定時dosomething函數中的第一個if判斷語句,因為其通過bird這個指針調用了foraging(覓食)這個函數;可見,當前傳入的這個obj指針所指向的對象就是一個Bird對象(如果這里我們指向的是一個Plane對象,那么顯而易見就會執行第二個if判斷語句,從而就會通過plane指針去調用carry(運輸)這個函數);最后一行打印出的是“Bird –> land()”,其是通過代碼obj->land();實現的。
這里,如果我們實例化一個Plane對象,并將對象指針傳入dosomething函數,如下:
int main() {Plane p;dosomething(&p);system("pause");return 0; }
運行結果:
從我們的打印結果就可以反推出RTTI所做的這些工作。
接下來,再通過一些代碼再來展示一下關于typeid以及dynamic_cast使用的注意事項。
我們先來看一看typeid,對于typeid來說,它能夠看任何一個對象或者指針的類型(包括基本的數據成員的類型)。比如,我們定義一個變量i,就可以通過cout來看一看我們定義的i究竟是什么類型,如下:
int main() {int i = 0;cout << typeid(i).name() << endl;system("pause");return 0; }
我們按F5來看一下運行結果:
打印結果就是int,這就說明i這個變量的數據類型就是int類型(如果我們寫成double i;),那么打印出來的就是double類型,如下:
那么,對于typeid來說,它能夠打印的指針是指針本身的類型。我們再來看一看typeid打印指針和打印對象的不同之處。
首先,我們用Flyable去定義一個指針p,并且用指針p去指向Bird這樣的一個對象(Flyable *p = new Bird();),指向這個對象之后,我們分別來看一看p和*p通過typeid所打印出來的結果如何,看如下代碼:
int main() {Flyable *p = new Bird();cout << typeid(p).name() << endl;cout << typeid(*p).name() << endl;system("pause");return 0; }
按一下F5,看一看運行結果:
我們看到,p通過typeid打印出來的結果是“class Flyable *”,也就是說,p是一個Flyable *的數據類型,而對于*p來說,它打印出來的則是“class Bird”,也就是說*p是一個Bird對象。
接著我們再來看一看dynamic_cast有什么使用限制。
為了看到這些限制,我們需要改造一下前面的代碼。
修改后的Flyable.h文件如下:
#ifndef FLYABLE_H #define FLYABLE_H//在Flyable這個類中定義兩個純虛函數takeoff()和land() class Flyable { public:void takeoff(){}void land(){} };#endif
修改后的Bird.h文件如下:
#ifndef Bird_H #define Bird_H#include "Flyable.h" #include <string> using namespace std;class Bird:public Flyable //公有繼承了Flyable { public:void foraging();//對于Bird類來說,其具有一個特有的成員函數foraging(覓食)void takeoff();void land(); };#endif
此時,對于Bird和Flyable來說,它們之間只是一種普通的子類和父類的關系
那么,當我們用父類的指針去指向一個子類的對象(Flyable *p = new Bird();)也是可以的。那么,我們還能不能通過dynamic_cast來進行指針的轉換呢?我們一起來看一看:
int main() {Flyable *p = new Bird();Bird *b = dynamic_cast<Bird *>p; //將Flyable的指針轉換為Bird指針,并且將轉換完的指針賦值給Bird的一個指針bsystem("pause");return 0; }
此時,我們按F7看一看編譯是否通過:
我們看到系統提示“dynamic_cast”:“Flyable”不是多態類型,也就是說,對于Flyable來說,它要求轉換的目標類型以及被轉換的數據類型都應該是具有虛函數的,如果沒有就會報錯;當然也不能直接轉對象這樣的類型,比如說,將Flyable的對象p直接轉換為Bird的對象b,如下:
int main() {Flyable p;Bird b = dynamic_cast<Bird>p;system("pause");return 0; }
我們看一看這樣是否可行,按F5:
我們看到,這樣依然會報錯,報錯提示依然是“dynamic_cast”:“Flyable”不是多態類型的原因,當然它也不是一個正常的數據類型,因為必須待是引用和指針才可能進行轉換,其次還要加上一個條件:這個類當中必須含有虛函數。