- 什麼是RTTI?
RTTI是 “run-time type identification”的縮寫,意思是運作時類型識别,它提供了運作時确定對象類型的方法。
RTTI的功能由兩個運算符實作:
1)dynamic_cast運算符,用于将基類的指針或引用安全轉換成派生類的指針或引用。
2)typeid運算符,用于傳回表達式類型。
我們想使用基類對象的指針或引用執行某個派生類操作,并且該操作不是虛函數。我們知道定義成虛函數,執行時會發生動态綁定,這也是我們希望的,但是并非任何時候都能定義成一個虛函數的。假設我們無法使用虛函數,則可以使用一個RTTI運算符,來達到相同的效果。另外,使用RTTI運算符蘊含着更多潛在的危險。程式員必須清楚地知道轉換的目标類型并且必須檢查類型轉換是否被成功執行。
例:
class A
{
public:
void fun1(){ cout << "i am A->fun1()" << endl; }
};
class B:public A
{
void fun1(){ cout << "i am B->fun1()" << endl; }
};
int main()
{
A* pa = new B;
pa->fun1();
return ;
}
事實上我們希望編輯器能自動識别到所指向對象類型,進而調用相對應的操作–其實這裡就是達到動态綁定的效果。
我們知道隻要把基類A中的fun1變成虛函數就可以實作。我們可以使用RTTI運算符來實作。
當然用之前我們先來分别學習這兩個運算符
-
dynameic_cast
dynamic_cast主要用于在多态的時候,它允許在運作時刻進行類型轉換,進而使程式能夠在一個類層次結構中安全地轉換類型,把基類指針(引用)轉換為派生類指針(引用)。
首先我們來看代碼:
A* pa = new B;
B* pb = dynamic_cast<B*>(pa);
if (pb == NULL)
{
cout << "error" << endl;
}
cout << typeid(*pb).name() << endl;
return ;
當我們使用dynamic_cast類型轉化後,類型就對了。在這裡面dynamic_cast做了什麼呢?
當類中存在虛函數時,編譯器就會在類的成員變量中添加一個指向虛函數表的vptr指針,每一個class所關聯的type_info object也經由virtual table被指出來,通常這個type_info object放在表格的第一個位置。當我們進行dynamic_cast時,編譯器會幫我們進行文法檢查。如果指針的靜态類型和目标類型相同,那麼就什麼事情都不做;否則,首先對指針進行調整,使得它指向vftable,并将其和調整之後的指針、調整的偏移量、靜态類型以及目标類型傳遞給内部函數。其中最後一個參數指明轉換的是指針還是引用。兩者唯一的差別是,如果轉換失敗,前者傳回NULL,後者抛出bad_cast異常。
class A
{
public:
virtual void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
void Print(){cout << "i am B" << endl;}
};
class C :public A
{
public:
void Print(){ cout << "i am C" << endl; }
};
void Handle(A* a)
{
if (dynamic_cast<B*>(a))
{
cout << "This is B" << endl;
}
else if (dynamic_cast<C*>(a))
{
cout << "This is C" << endl;
}
else
{
cout << "error" << endl;
}
}
int main()
{
A* pa = new B;
Handle(pa);
return ;
}
-
typeid
typeid表達式的形式是typeid(e),e可以為任意表達式或名字,内置類型或自定義類型都可以。
struct A
{
};
int main()
{
int a;
char c;
A _a;
cout << typeid(a).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(_a).name() << endl;
return 0;
}
輸出 int char struct A,說明它支援内置類型,同時也支援自定義類型。
我們發現當我們調用typeid後通常會調用name()函數,這說明typeid傳回的是一個結構體或者類,然後通過類或者結構體來調用name()成員。其實typeid傳回的是一個type_info類型的對象。type_info類定義在typeinfo頭檔案中。既然typeid傳回的是一個type_info,我們就要對type_info有所了解
class type_info
{
public:
virtual ~type_info();
bool operator==(const type_info& _Rhs) const; // 用于比較兩個對象的類型是否相等
bool operator!=(const type_info& _Rhs) const; // 用于比較兩個對象的類型是否不相等
bool before(const type_info& _Rhs) const;
// 傳回對象的類型名字,這個函數用的很多
const char* name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;
const char* raw_name() const;
private:
void *_M_data;
char _M_d_name[];
type_info(const type_info& _Rhs);
type_info& operator=(const type_info& _Rhs);
static const char * _Name_base(const type_info * __type_info_node* __ptype_info_node);
static void _Type_info_dtor(type_info *);
};
去掉那些多餘的宏,這是VS2013中type_info的定義。
type_info中沒有預設構造函數,同時可以看到它的拷貝構造和指派運算符都是私有的,那麼我們就無法定義或拷貝type_info類型額度對象,也不能為type_info類型對象指派。
type_info類的name成員函數傳回一個C風格的字元串,表示對象類型名字。
操作:
t1==t2
t1和t2表示同一類型,傳回true;否則傳回false
t1!=t2
t1和t3表示不同類型,傳回true;否則傳回false
t.name()
傳回一個C風格字元串,表示類型名字。
t1.before(t2)
傳回一個bool值,表示t1是否在t2之前。
typeid
如果操作數不是類類型或者是沒有虛函數的類,則擷取其靜态類型;如果操作數是定義了虛函數的類類型,則計算運作時類型。
typeid的使用:
1)使用type_info中的name()成員傳回類型名稱
這裡有一點需要注意,我們來看代碼
class A
{
public:
void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
void Print(){cout << "i am B" << endl;}
};
int main()
{
A* pa = new B;
cout << typeid(pa).name() << endl;
cout << typeid(*pa).name() << endl;
return ;
}
輸出 class A * class A
我們發現調用兩次typeid,參數不同,傳回的類型也不同,當我們傳入pa時,pa是一個A*類型的指針,是以typeid計算出pa的類型為class A*,第二次傳入*pa,傳入的是一個對象,是以輸出的是class A。
同時我們又看到我們讓一個A類型的指針,指向一個B,可是得到的還是A類型,也就是它的指針所指對象類型。注意這裡我們沒有定義成虛函數。
那我們把Print定義成虛函數會發生什麼呢?
class A
{
public:
virtual void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
void Print(){cout << "i am B" << endl;}
};
int main()
{
A* pa = new B;
cout << typeid(*pa).name() << endl;
return ;
}
輸出 class B
這是為什麼呢?這就是因為RTTI,當我們沒有将Print變成虛函數時,typeid是在編譯期間,檢測得到的是它的靜态類型,就像剛開始輸出的class A一樣,當類中存在虛函數時,typeid是在運作期間,得到的是它的動态類型,是以輸出class B。
2)使用type_info的==和!=
class A
{
public:
virtual void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
void Print(){cout << "i am B" << endl;}
};
class C :public A
{
public:
void Print(){ cout << "i am C" << endl; }
};
void Handle(A* a)
{
if (typeid(*a) == typeid(A))
{
cout << "This is A" << endl;
}
else if (typeid(*a) == typeid(B))
{
cout << "This is B" << endl;
}
else if (typeid(*a) == typeid(C))
{
cout << "This is C" << endl;
}
else
{
cout << "error" << endl;
}
}
int main()
{
A* pa = new B;
Handle(pa);
//cout << typeid(*pa).name() << endl;
delete pa;
pa = new C;
Handle(pa);
return ;
}
輸出 This is B This is C
-
使用RTTI
在某些情況下RTTI非常有用,比如當我們想為具有繼承關系的類實作相運算符時。
一種容易想到的方法是定義一套虛函數,令其在繼承體系的各個層次分别執行相等判斷。但是虛函數的基類版本和派生類版本必須具有相同的形參類型。
如果想實作“==”操作符。假設類層次中隻有2個類型,那麼需要4個函數:
bool operator==(const Base&, const Base&)
bool operator==(const Derived&, const Derived &)
bool operator==(const Derived &, const Base&)
bool operator==(const Base&, const Derived &)
如果類層次中有4個類型,就要實作16個操作符函數,這種實作就太麻煩了。
其實我們可以使用RTTI解決這個問題。
我們定義的相等運算符的形參設定為基類的引用,然後在用typeid或dynamic_cast檢查類型是否一緻,一緻的話傳回true,否則傳回false,然後如果類型一緻則調用equal函數。
隻定義1個“==”操作符函數,每個類定義一個虛函數equal。
#include<iostream>
#include<typeinfo>
using namespace std;
class Base
{
friend bool operator==(const Base&, const Base&);
public:
// interface members for Base
protected:
virtual bool equal(const Base&) const;
// data and other implementation members of Base
};
bool Base::equal(const Base &rhs) const
{
// do whatever is required to compare to Base objects
return true;
}
class Derived : public Base
{
friend bool operator==(const Base&, const Base&);
public:
// other interface members for Derived
private:
bool equal(const Base&) const;
// data and other implementation members of Derived
};
bool Derived::equal(const Base &rhs) const
{
if (const Derived *dp = dynamic_cast<const Derived *>(&rhs))
{
// do work to compare two Derived objects and return result
return true;
}
else
return false;
}
bool operator==(const Base &lhs, const Base &rhs)
{
//如果類型一緻調用equal
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
int main()
{
Base* pa = new Derived;
Derived d;
if (d==*pa)
{
cout << "RTTI" << endl;
}
return ;
}