C++運(yùn)行時(shí)類型識(shí)別與轉(zhuǎn)換實(shí)現(xiàn)方法
當(dāng)僅有一個(gè)指針或引用指向基類型時(shí),利用運(yùn)行時(shí)類型識(shí)別(RTTI)可以找到一個(gè)對(duì)象的動(dòng)態(tài)類型。
運(yùn)行時(shí)類型識(shí)別可能被認(rèn)為是C++中一個(gè)”次要“的特征,當(dāng)程序員在編程過程中陷入非常困難的境地時(shí),實(shí)用主義將會(huì)幫助他走出困境。正常情況下,程序員需要有意忽略對(duì)象的準(zhǔn)確類型,而利用虛函數(shù)機(jī)制實(shí)現(xiàn)那個(gè)類型正確操作過程。然而,有時(shí)知道一個(gè)僅含一個(gè)基類指針的對(duì)象的準(zhǔn)確的運(yùn)行時(shí)類型(即多半是派生的類型)是非常有用的。有了此信息,就可以更有效地進(jìn)行某些特殊情況的操作,或者預(yù)防基類接口因無此信息而變得笨拙。
1.運(yùn)行時(shí)類型轉(zhuǎn)換
通過指針或者引用決定對(duì)象運(yùn)行時(shí)類型的一種方法是使用運(yùn)行時(shí)類型轉(zhuǎn)換(runtime cast),用這種方法可以查證所嘗試進(jìn)行的轉(zhuǎn)換正確與否。當(dāng)要把基類指針類型轉(zhuǎn)換為派生類時(shí),這個(gè)方法非常有用。由于繼承的層次結(jié)構(gòu)的典型描述說基類在派生類之上,所以這種類型轉(zhuǎn)換也成為向下類型轉(zhuǎn)換(downcast)。
請(qǐng)看下面的類層次結(jié)構(gòu):
在下面的程序代碼中,Investment類中有一個(gè)其他類沒有的額外操作,所以能夠運(yùn)行時(shí)知道Security指針是否引用了Investment對(duì)象是很重要的。為了實(shí)現(xiàn)檢查運(yùn)行時(shí)的類型轉(zhuǎn)換,每個(gè)類都持有一個(gè)整數(shù)標(biāo)識(shí)符,以便可以與層次結(jié)構(gòu)中其他的類區(qū)別開來。
#include <iostream> #include <vector> #include "../purge.h" using namespace std; class Security { protected: enum { BASEID = 0 }; public: virtual ~Security() {} virtual bool isA(int id) { return (id == BASEID); } }; class Stock : public Security { typedef Security Super; protected: enum { OFFSET = 1, TYPEID = BASEID + OFFSET }; public: bool isA(int id) { return id == TYPEID || Super::isA(id); } static Stock* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Stock*>(s) : 0; } }; class Bond : public Security { typedef Security Super; protected: enum { OFFSET = 2, TYPEID = BASEID + OFFSET }; public: bool isA(int id) { return id == TYPEID || Super::isA(id); } static Bond* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Bond*>(s) : 0; } }; class Investment : public Security { typedef Security Super; protected: enum { OFFSET = 3, TYPEID = BASEID + OFFSET }; public: bool isA(int id) { return id == TYPEID || Super::isA(id); } static Investment* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Investment*>(s) : 0; } void special() { cout << "special Investment function" << endl; } }; class Metal : public Investment { typedef Investment Super; protected: enum { OFFSET = 4, TYPEID = BASEID + OFFSET }; public: bool isA(int id) { return id == TYPEID || Super::isA(id); } static Metal* dynacast(Security* s) { return (s->isA(TYPEID)) ? static_cast<Metal*>(s) : 0; } }; int main() { vector<Security*> portfolio; portfolio.push_back(new Metal); portfolio.push_back(new Investment); portfolio.push_back(new Bond); portfolio.push_back(new Stock); for(vector<Security*>::iterator it = portfolio.begin(); it != portfolio.end(); ++it) { Investment* cm = Investment::dynacast(*it); if(cm) cm->special(); else cout << "not an Investment" << endl; } cout << "cast from intermediate pointer:" << endl; Security* sp = new Metal; Investment* cp = Investment::dynacast(sp); if(cp) cout << " it's an Investment" << endl; Metal* mp = Metal::dynacast(sp); if(mp) cout << " it's a Metal too!" << endl; purge(portfolio); } ///:~
多態(tài)的isA()函數(shù)檢查其參數(shù)是否與它的類型參數(shù)(id)相容,就意味著或者id與對(duì)象的typeID準(zhǔn)確地匹配,或者與對(duì)象的祖先之一的類型匹配(因?yàn)樵谶@種情況下調(diào)用Super::isA())。函數(shù)dynacast()在每個(gè)類中都是靜態(tài)的,dynacast()為其指針參數(shù)調(diào)用isA()來檢查類型轉(zhuǎn)換是否有效。
借助dynamic_cast操作符,C++提供這樣一個(gè)可檢查的類型轉(zhuǎn)換。使用dynamic_cast對(duì)前面的程序例子進(jìn)行重寫,就得到下面的程序:
//: C08:Security.h #ifndef SECURITY_H #define SECURITY_H #include <iostream> class Security { public: virtual ~Security() {} }; class Stock : public Security {}; class Bond : public Security {}; class Investment : public Security { public: void special() { std::cout << "special Investment function" <<std::endl; } }; class Metal : public Investment {}; #endif // SECURITY_H ///:~
// Uses RTTI's dynamic_cast. #include <vector> #include "../purge.h" #include "Security.h" using namespace std; int main() { vector<Security*> portfolio; portfolio.push_back(new Metal); portfolio.push_back(new Investment); portfolio.push_back(new Bond); portfolio.push_back(new Stock); for(vector<Security*>::iterator it = portfolio.begin(); it != portfolio.end(); ++it) { Investment* cm = dynamic_cast<Investment*>(*it); if(cm) cm->special(); else cout << "not a Investment" << endl; } cout << "cast from intermediate pointer:" << endl; Security* sp = new Metal; Investment* cp = dynamic_cast<Investment*>(sp); if(cp) cout << " it's an Investment" << endl; Metal* mp = dynamic_cast<Metal*>(sp); if(mp) cout << " it's a Metal too!" << endl; purge(portfolio); } ///:~
由于原來例子中大部分的代碼開銷用在了類型轉(zhuǎn)換檢查上,所以這個(gè)例子就變得如此之短。如果想要安全地進(jìn)行向下類型轉(zhuǎn)換,dynamic_cast要求使用的目標(biāo)類型是多態(tài)的(polymorphic)。這就要求該類必須至少有一個(gè)虛函數(shù)。幸運(yùn)的是,Security基類有一個(gè)虛析構(gòu)函數(shù),所以這里不需要再創(chuàng)建一個(gè)額外的函數(shù)去做這項(xiàng)工作。因?yàn)閐ynamic_cast在程序運(yùn)行時(shí)使用了虛函數(shù)表,所以比其他新式風(fēng)格的類型轉(zhuǎn)換操作來說它的代價(jià)更高。
用引用而非指針同樣也可以使用dynamic_cast,但是由于沒有諸如空引用這樣的情況,這就需要采用其他方法來了解類型轉(zhuǎn)換是否失敗。這個(gè)”其他方法“就是捕獲bad_cast異常,如下所示:
#include <typeinfo> #include "Security.h" using namespace std; int main() { Metal m; Security& s = m; try { Investment& c = dynamic_cast<Investment&>(s); cout << "It's an Investment" << endl; } catch(bad_cast&) { cout << "s is not an Investment type" << endl; } try { Bond& b = dynamic_cast<Bond&>(s); cout << "It's a Bond" << endl; } catch(bad_cast&) { cout << "It's not a Bond type" << endl; } } ///:~
2.typeid操作符
獲得有關(guān)一個(gè)對(duì)象運(yùn)行時(shí)信息的另一個(gè)方法,就是typeid操作符來完成。這種操作符返回一個(gè)type_info類的對(duì)象,該對(duì)象給出與其應(yīng)用有關(guān)的對(duì)象類型的信息。如果該對(duì)象的類型是多態(tài)的,它將給出那個(gè)應(yīng)用(動(dòng)態(tài)類型(dynamic type))的大部分派生類信息;否則,它將給出靜態(tài)類型信息。typeid操作符的一個(gè)用途是獲得一個(gè)對(duì)象的動(dòng)態(tài)類型的名稱,例如const char * ,就像在下面例子中可以看到的:
#include <iostream> #include <typeinfo> using namespace std; struct PolyBase { virtual ~PolyBase() {} }; struct PolyDer : PolyBase { PolyDer() {} }; struct NonPolyBase {}; struct NonPolyDer : NonPolyBase { NonPolyDer(int) {} }; int main() { // Test polymorphic Types const PolyDer pd; const PolyBase* ppb = &pd; cout << typeid(ppb).name() << endl; cout << typeid(*ppb).name() << endl; cout << boolalpha << (typeid(*ppb) == typeid(pd)) << endl; cout << (typeid(PolyDer) == typeid(const PolyDer)) << endl; // Test non-polymorphic Types const NonPolyDer npd(1); const NonPolyBase* nppb = &npd; cout << typeid(nppb).name() << endl; cout << typeid(*nppb).name() << endl; cout << (typeid(*nppb) == typeid(npd)) << endl; // Test a built-in type int i; cout << typeid(i).name() << endl; } ///:~
這是使用一個(gè)特定編譯器的程序的輸出是:
struct PolyBase const *
struct Polyder
true
true
struct NonPolyBase const *
struct NonPolyBase
false
int
因?yàn)閜pb是一個(gè)指針,所以輸出的第1行是他的靜態(tài)類型。為了在程序中得到RTTI的結(jié)果,需要檢查指針或引用目標(biāo)對(duì)象,這在第2行說明。需要注意的是,RTTI忽略了頂層的const和volatile限定符。借助非多態(tài)類型,正好可以獲得靜態(tài)類型(該指針本身的類型)。正如讀者所見,這里也支持內(nèi)置類型。
2.1類型轉(zhuǎn)換到中間層次類型
#include <cassert> #include <typeinfo> using namespace std; class B1 { public: virtual ~B1() {} }; class B2 { public: virtual ~B2() {} }; class MI : public B1, public B2 {}; class Mi2 : public MI {}; int main() { B2* b2 = new Mi2; Mi2* mi2 = dynamic_cast<Mi2*>(b2); MI* mi = dynamic_cast<MI*>(b2); B1* b1 = dynamic_cast<B1*>(b2); assert(typeid(b2) != typeid(Mi2*)); assert(typeid(b2) == typeid(B2*)); delete b2; } ///:~
如果創(chuàng)建一個(gè)Mi2對(duì)象并將它向上類型轉(zhuǎn)換到該繼承層次結(jié)構(gòu)的根(在這種情況下,選擇兩個(gè)可能的根中的一個(gè)),可以成功地使dynamic_cast回退至兩個(gè)派生層MI或Mi2中的任何一個(gè)。
甚至可以從一個(gè)根到另一個(gè)根進(jìn)行類型轉(zhuǎn)換:
B1* b1 = dynamic_cast<B1*>(b2);
這也是成功的,因?yàn)锽2實(shí)際上指向一個(gè)Mi2對(duì)象,該Mi2對(duì)象含有一個(gè)B1類型的子對(duì)象。
2.2void型指針
RTTI僅僅為完整的類型工作,這就意味著當(dāng)使用typeid時(shí),所有的類型信息必須是可利用的。特別是,它不能與void型指針一起工作:
//!#include <iostream> #include <typeinfo> using namespace std; class Stimpy { public: virtual void happy() {} virtual void joy() {} virtual ~Stimpy() {} }; int main() { void* v = new Stimpy; // Error: //! Stimpy* s = dynamic_cast<Stimpy*>(v); // Error: //! cout << typeid(*v).name() << endl; } ///:~
一個(gè)void* 真是的意思是”無類型信息“。
2.3運(yùn)用帶模板的RTTI
因?yàn)樗械念惸0逅龅墓ぷ骶褪钱a(chǎn)生類,所以類模板可以很好地與RTTI一起工作。RTTI提供了一條方便的途徑來獲得對(duì)象所在類的名稱。下面的示例打印出構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用順序:
#include <iostream> #include <typeinfo> using namespace std; template<int id> class Announce { public: Announce() { cout << typeid(*this).name() << " constructor" << endl; } ~Announce() { cout << typeid(*this).name() << " destructor" << endl; } }; class X : public Announce<0> { Announce<1> m1; Announce<2> m2; public: X() { cout << "X::X()" << endl; } ~X() { cout << "X::~X()" << endl; } }; int main() { X x; } ///:~
在構(gòu)造函數(shù)內(nèi)和析構(gòu)函數(shù)內(nèi)部,RTTI信息產(chǎn)生打印的類名。類X利用繼承和組合兩個(gè)方式創(chuàng)建一個(gè)類。輸出如下:
Announce<0> constructor
Announce<1> constructor
Announce<2> constructor
X::X()
X::~X()
Announce<2> destructor
Announce<1> destructor
Announce<0> destructor
當(dāng)然,可能會(huì)得到不同的結(jié)果,這取決于編譯器如何表示它的name()信息。
3.多重繼承
RTTI機(jī)制必須正確地處理多重繼承的所有復(fù)雜性,包括虛基類virtual:
#include <iostream> #include <typeinfo> using namespace std; class BB { public: virtual void f() {} virtual ~BB() {} }; class B1 : virtual public BB {}; class B2 : virtual public BB {}; class MI : public B1, public B2 {}; int main() { BB* bbp = new MI; // Upcast // Proper name detection: cout << typeid(*bbp).name() << endl; // Dynamic_cast works properly: MI* mip = dynamic_cast<MI*>(bbp); // Can't force old-style cast: //! MI* mip2 = (MI*)bbp; // Compile error } ///:~
typeid()操作符正確地檢測(cè)出實(shí)際對(duì)象的名字,即便它采用virtual基類指針完成這個(gè)任務(wù)的,dynamic_cast也正確地進(jìn)行工作。但實(shí)際上,編譯器不允許程序員用以前的方法嘗試強(qiáng)制進(jìn)行類型轉(zhuǎn)換:
MI* mip2 = (MI*)bbp; // Compile error
編譯器知道這樣做絕不是正確的方法,因此需要程序員使用dynamic_cast。
4.合理使用RTTI
垃圾再生器
為了進(jìn)一步地舉例說明RTTI的實(shí)際用途,下面的程序模擬了一個(gè)垃圾再生器。不同種類的”垃圾“被插入一個(gè)容器中,然后根據(jù)它們的動(dòng)態(tài)類型進(jìn)行分類。
// Describing trash. #ifndef TRASH_H #define TRASH_H #include <iostream> class Trash { float _weight; public: Trash(float wt) : _weight(wt) {} virtual float value() const = 0; float weight() const { return _weight; } virtual ~Trash() { std::cout << "~Trash()" << std::endl; } }; class Aluminum : public Trash { static float val; public: Aluminum(float wt) : Trash(wt) {} float value() const { return val; } static void value(float newval) { val = newval; } }; class Paper : public Trash { static float val; public: Paper(float wt) : Trash(wt) {} float value() const { return val; } static void value(float newval) { val = newval; } }; class Glass : public Trash { static float val; public: Glass(float wt) : Trash(wt) {} float value() const { return val; } static void value(float newval) { val = newval; } }; #endif // TRASH_H ///:~
用來表示垃圾類型單價(jià)的static值定義在實(shí)現(xiàn)文件中:
// A Trash Recycler. #include "Trash.h" float Aluminum::val = 1.67; float Paper::val = 0.10; float Glass::val = 0.23; ///:~
sunValue()模板從頭到尾對(duì)一個(gè)容器進(jìn)行迭代,顯示并計(jì)算結(jié)果:
//{L} Trash // A Trash Recycler. #include <cstdlib> #include <ctime> #include <iostream> #include <typeinfo> #include <vector> #include "Trash.h" #include "../purge.h" using namespace std; // Sums up the value of the Trash in a bin: template<class Container> void sumValue(Container& bin, ostream& os) { typename Container::iterator tally = bin.begin(); float val = 0; while(tally != bin.end()) { val += (*tally)->weight() * (*tally)->value(); os << "weight of " << typeid(**tally).name() << " = " << (*tally)->weight() << endl; ++tally; } os << "Total value = " << val << endl; } int main() { srand(time(0)); // Seed the random number generator vector<Trash*> bin; // Fill up the Trash bin: for(int i = 0; i < 30; i++) switch(rand() % 3) { case 0 : bin.push_back(new Aluminum((rand() % 1000)/10.0)); break; case 1 : bin.push_back(new Paper((rand() % 1000)/10.0)); break; case 2 : bin.push_back(new Glass((rand() % 1000)/10.0)); break; } // Note: bins hold exact type of object, not base type: vector<Glass*> glassBin; vector<Paper*> paperBin; vector<Aluminum*> alumBin; vector<Trash*>::iterator sorter = bin.begin(); // Sort the Trash: while(sorter != bin.end()) { Aluminum* ap = dynamic_cast<Aluminum*>(*sorter); Paper* pp = dynamic_cast<Paper*>(*sorter); Glass* gp = dynamic_cast<Glass*>(*sorter); if(ap) alumBin.push_back(ap); else if(pp) paperBin.push_back(pp); else if(gp) glassBin.push_back(gp); ++sorter; } sumValue(alumBin, cout); sumValue(paperBin, cout); sumValue(glassBin, cout); sumValue(bin, cout); purge(bin); } ///:~
因?yàn)槔徊患臃诸惖赝度氲揭粋€(gè)容器,這樣一來,垃圾的所有信息就”丟失“了。但是,為了稍后適當(dāng)?shù)貙?duì)廢料進(jìn)行分類,具體類型信息必須恢復(fù),這將用到RTTI。
可以通過使用map來改進(jìn)這種解決方案,該map將指向type_info對(duì)象的指針與一個(gè)包含Trash指針的vector關(guān)聯(lián)起來。因?yàn)橛诚裥枰粋€(gè)能識(shí)別排序的判定函數(shù),這里提供了一個(gè)名為TInfoLess的結(jié)構(gòu),它調(diào)用type_info::before()。注意,這里必須對(duì)sumValue()進(jìn)行不同的定義。
//{L} Trash // Recyling with a map. #include <cstdlib> #include <ctime> #include <iostream> #include <map> #include <typeinfo> #include <utility> #include <vector> #include "Trash.h" #include "../purge.h" using namespace std; // Comparator for type_info pointers struct TInfoLess { bool operator()(const type_info* t1, const type_info* t2) const { return t1->before(*t2); } }; typedef map<const type_info*, vector<Trash*>, TInfoLess> TrashMap; // Sums up the value of the Trash in a bin: void sumValue(const TrashMap::value_type& p, ostream& os) { vector<Trash*>::const_iterator tally = p.second.begin(); float val = 0; while(tally != p.second.end()) { val += (*tally)->weight() * (*tally)->value(); os << "weight of " << p.first->name() // type_info::name() << " = " << (*tally)->weight() << endl; ++tally; } os << "Total value = " << val << endl; } int main() { srand(time(0)); // Seed the random number generator TrashMap bin; // Fill up the Trash bin: for(int i = 0; i < 30; i++) { Trash* tp; switch(rand() % 3) { case 0 : tp = new Aluminum((rand() % 1000)/10.0); break; case 1 : tp = new Paper((rand() % 1000)/10.0); break; case 2 : tp = new Glass((rand() % 1000)/10.0); break; } bin[&typeid(*tp)].push_back(tp); } // Print sorted results for(TrashMap::iterator p = bin.begin(); p != bin.end(); ++p) { sumValue(*p, cout); purge(p->second); } } ///:~
為了直接調(diào)用type_info::name(),我們?cè)谶@里修改了sunValue(),因?yàn)樽鳛門rashMap::value_type對(duì)的第1個(gè)成員,type_info對(duì)象現(xiàn)在是可獲得的。這樣就避免了為了獲得正在處理的Trash的類型名而額外調(diào)用typeid,而這在該程序的以前版本中卻是必須做的。
5.RTTI的機(jī)制和開銷
實(shí)現(xiàn)RTTI典型的方法是,通過在類的虛函數(shù)表中放置一個(gè)附加的指針。這個(gè)指針指向那個(gè)特別類型的type_info結(jié)構(gòu)。typeid()表達(dá)式的結(jié)果非常簡(jiǎn)單:虛函數(shù)表指針取得type_info指針,并且產(chǎn)生一個(gè)對(duì)type_info結(jié)構(gòu)的引用。因?yàn)檫@正好是一個(gè)雙指針的解析操作,這是一個(gè)代價(jià)為常量時(shí)間的操作。
6.小結(jié)
盡管通常情況下會(huì)為一個(gè)指向其基類的指針進(jìn)行向上類型轉(zhuǎn)換,然后再使用那個(gè)基類的通用接口(通過虛函數(shù)),但是如果知道一個(gè)由基類指針指向的對(duì)象的動(dòng)態(tài)類型,有時(shí)候根據(jù)獲得的這些信息進(jìn)行相關(guān)處理可能會(huì)使事情變得更加有效,而這些正是RTTI所提供的。大部分通常的誤用來自一些程序員,這些誤用是由于他們不理解虛函數(shù)而實(shí)采用RTTI來做類型檢查的編碼所造成的。C++的基本原理似乎提供了對(duì)違反類型的定義規(guī)則和完整性的情況進(jìn)行監(jiān)督和糾正的強(qiáng)有力的工具和保護(hù),但是如果有誰想故意誤用或回避某一語言的特征,那么將沒有人可以組織他這樣做。
到此這篇關(guān)于C++運(yùn)行時(shí)類型識(shí)別與轉(zhuǎn)換實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)C++類型識(shí)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Qt制作一個(gè)定時(shí)關(guān)機(jī)的小程序
這篇文章主要為大家詳細(xì)介紹了如何基于Qt制作一個(gè)有趣的定時(shí)關(guān)機(jī)的小程序,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12詳解Matlab繪制3D玫瑰花的方法(內(nèi)附旋轉(zhuǎn)版本)
這篇文章主要為大家介紹了如何利用Matlab繪制3D版的玫瑰花以及旋轉(zhuǎn)版的3D玫瑰花,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手試一試2022-03-03c++代碼實(shí)現(xiàn)tea加密算法的實(shí)例詳解
這篇文章主要介紹了c++代碼實(shí)現(xiàn)tea加密算法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04