C++之 RTTI
什么是 RTTI
RTTI(Runtime Type Indentification) 即運行階段類型識別。 這是 C++新引進的特性之一。RTTI旨在為程序在運行階段確定對象的類型提供一種標準方式。
RTTI 用途
考慮一種情況:假如現在有一個基類 Base,并且基類中包含有虛函數 Fun1,然后派生類 A 繼承于基類 Base,并實現基類的虛函數Fun1,同時,派生類 A 又定義一個虛函數 Fun2,接著,派生類 B 繼承于 A,并且實現了基類中的兩個虛函數 Fun1和 Fun2,那么,當我們選擇一個類 A 或 B,創建一個對象,并將指針賦值給基類指針,這時候,當我們要用改指針的時候就不知道該指針真正指向哪個對象。
那么問題來了,可能你會問,干嘛要知道該指針類型呢?如果說通過該指針去調用基類本身就有的虛函數,則可以正常調用,不需要知道對象的類型,但是當調用派生類中新定義的函數,這時候就會出錯啦。這種情況下,只有某些類型的對象可以使用該方法。所以就需要進行判斷對象類型,或者進行相應的強制合理轉換對象類型。
光是描述可能太難理解,現在咱來看看實際的例子:
#include <iostream>using namespace std;class Base { public:virtual void Fun1(){}virtual ~Base(); };class A : public Base { public:virtual void Fun1(){}virtual void Fun2(){}virtual ~A(); };class B :public A { public:void Fun1(){}void Fun2(){}virtual ~B(); };int main() {Base * p1;Base * p2;Base * p3;p1 = new Base();p2 = new A();p3 = new B();p2->Fun1();//right // p2->Fun2();//error..p3->Fun1();//right // p3->Fun2();//error..return 0; }Base::~Base(){}A::~A(){}B::~B(){}上例子可以看到,p2->Fun2()以及p3->Fun2()是會報錯的,因 Fun2()是在 A 類中定義的,而 p2指針是賦給基類 Base 的,Base 中并沒有 Fun2()函數。
對于這種問題,RTTI 提供了解決方案。
C++中三個支持 RTTI 的元素
- dynamic_cast :改運算符將使用一個指向基類的指針來生成一個指向派生類的指針。如果轉換不合理,會轉換失敗,并返回0—空指針。
- typeid:運算符返回一個指出對象的類型的值。
- type_info:存儲了有關特定類型的信息。
注意:RTTI 只適用于包含虛函數的類。只能講 RTTI 用于包含虛函數的類層次結構,原因在于只有對于這種類層次結構,才應將派生對象的地址賦給基類指針。
dynamic_cast
dynamic_cast運算符是最常見的 RTTI 組件,它可以安全地將對象的地址賦給特定類型的指針。
還是根據以上示例,咱們來看以下幾個轉換是否安全:
Base * pBase = new Base(); A * pA = new A(); B * pB = new B();B * p1 = (B*)pB; //#1 B * p2 = (B*)pBase; //#2 A * p3 = (B*)pB; //#3上面示例中#2是不安全的,因為只有那些指針類型與對象的類型(或對象的直接或間接基類的類型)相同的類型轉換才一定是安全的。#2的轉換中,希望將基類指針轉換成派生類指針,但是由于基類指針 Base 中并不具有 B 類中的 Fun2()函數,所以這個轉換就是不安全的。而#1和#3可以安全轉換。
那么,我們怎么來檢查這些轉換是否安全呢?這時候dynamic_cast就排上用場了。
看一個通用的轉換示例:
如果只想的對象(*pt)的類型為 Type 或者從 Type 直接或間接派生而來的類型,則以上表達式將指針 pt 轉換成 Type 類型的指針,否則,轉換失敗,返回空指針0.
也就是說dynamic_cast會在轉換的時候動態的進行類型安全檢查,如果該轉換安全,那么就正常轉換,如果不安全,就返回空指針。
我們在將上面的#2表達式修改如下:
B * p2 = dynamic_cast<B*>(pBase);這個轉換不安全,所以p2的值為空。
OK,接下來看一個正式的例子:
#include <iostream> #include <cstdlib> #include <ctime>using namespace std;class Grand { public:Grand(int h = 0):hold(h){}virtual void Speak() const {cout << "I'm a grand class! \n";}virtual int Value() const{return hold;}virtual ~Grand(); private:int hold; };class Superb : public Grand { public:Superb(int h = 0):Grand(h){}void Speak() const {cout << "I'm a superb class \n";}virtual void Say() const{cout << "I hold the superb value of " << Value() << "!\n";}virtual ~Superb();};class Magnificent: public Superb { public:Magnificent(int h = 0,char c = 'A'): Superb(h), ch(c){}void Speak() const {cout << "I'm a magnificent class !!\n";}void Say() const {cout << "I hold the character" << ch<< "and the integer " << Value() << "!\n";}private:char ch; };Grand * GetOne() {Grand * p = nullptr;switch(std::rand() %3){case 0: p = new Grand(std::rand() % 100);break;case 1: p = new Superb(std::rand() % 100);break;case 2: p = new Magnificent(std::rand() % 100,'A' + std::rand() % 26);break;}return p; }int main() {std::srand(std::time(0));Grand * pg;Superb * ps;for(int i = 0 ; i < 5 ; i++){pg = GetOne();pg->Speak();if(ps = dynamic_cast<Superb*>(pg)){ps->Say();}}return 0; }Grand::~Grand(){}Superb::~Superb(){}輸出:
I'm a grand class! I'm a superb class I hold the superb value of 64! I'm a magnificent class !! I hold the characterUand the integer 71! I'm a grand class! I'm a magnificent class !! I hold the characterCand the integer 51!這個示例中,GetOne()方法隨機創建一個指針,并賦值給基類Grand指針,在 main 函數中pg->Speak();可以正常調用,是因為 Speak()方法本身就是在基類中定義的,而Say()方法是在派生類中定義的,基類指針無法調用,所以這里加入了動態轉換 dynamic_cast<Superb*>(pg),如果轉換合法,將會正常調用 Say()方法,如果不合法,則返回空指針。
所以根據輸出結果我們看到,如果隨機創建出來的是基類指針,那么不會調用 Say()方法。
typeid運算符和 type_info類
typeid運算符使得能夠確定兩個對象是否為同種類型。它可以接受兩種參數:
- 1.類名
- 2.結果為對象的表達式
typeid 運算符返回一個對type_info對象的引用,其中,type_info是在頭文件 typeinfo 中定義的一個類。type_info類重載了==和 != 運算符,以便可以使用這些運算符來對類型進行比較。
OK,接下來將上面的示例修改一下:
int main() {std::srand(std::time(0));Grand * pg;Superb * ps;for(int i = 0 ; i < 5 ; i++){pg = GetOne();pg->Speak();if(ps = dynamic_cast<Superb*>(pg)){ps->Say();}//類型判斷if(typeid (Magnificent) == typeid (*pg)){cout<< "Yes, you are really Manificent.\n";}}return 0; }運行結果:
I'm a grand class! I'm a magnificent class !! I hold the characterBand the integer 32! Yes, you are really Manificent. I'm a grand class! I'm a magnificent class !! I hold the characterGand the integer 95! Yes, you are really Manificent. I'm a grand class!上述代碼中,typeid (Magnificent) == typeid (*pg),如果 pg 指向的是Magnificent對象,則該表達式的結果為 true,否則為 false。如果 pg 是一個空指針,程序將引發 bad_typeid異常。該異常類型是從 exception 類派生而來,是在頭文件 typeinfo 中聲明的。
type_info類的實現隨編譯器廠商而異,但包含一個 name()成員,該函數返回一個隨實現而異的字符串:通常(但并非一定)是類 的名稱。
為了查看效果,我們繼續在上述代碼中添加打印輸出:
結果:
I'm a magnificent class !! I hold the characterKand the integer 59! typeid (*pg) = 11Magnificent Yes, you are really Manificent. I'm a grand class! typeid (*pg) = 5Grand I'm a grand class! typeid (*pg) = 5Grand I'm a superb class I hold the superb value of 36! typeid (*pg) = 6Superb I'm a magnificent class !! I hold the characterOand the integer 49! typeid (*pg) = 11Magnificent Yes, you are really Manificent.總結
- 上一篇: C++ warning:’xxx‘ ha
- 下一篇: C++之dynamic_cast、sta