1. 程式人生 > >7.3 執行期型別識別(Runtime Type Identification,RTTI)

7.3 執行期型別識別(Runtime Type Identification,RTTI)

Type-Safe Downcast(保證安全的向下轉換操作)

C++被吹毛求疵的一點,它缺乏一個保證安全的downcast(向下轉換操作)。只有在“型別真的可以被適當轉換”的情況下,你才能夠執行downcast。一個type-safe downcast必須在執行期對指標所有查詢,看看它是否指向它所展現之object真正的型別。因此,欲支援type-safe downcast,在object空間和執行時間上都需要一些額外的負擔:

  • 額外的空間儲存型別資訊(type information),通常是一個指標,指向某個型別資訊節點。
  • 額外的時間以決定執行期的型別(runtime tpye),這需要在執行期才能決定。

這種機制對於下面的C結構,會如何影響:

char* winnie_tbl[] = {"rumbly in my tummy","on,bother"};

會導致空間和效率上的不良後果:

  1. 程式設計師大量使用多型,並因此需要大量的downcast。
  2. 程式設計師使用內建資料型別以及非多型,就不會需要額外的負擔所帶來的不良結果。

C++的RTTI提供了一個安全的downcast,但只是針對那些展現(多型)的型別有效。分辨一個class的定義就是覺得這個class用以表現一個獨立的ADT還是一個支援多型的可繼承子型別(subtype)。策略之一就是匯入一個新的關鍵詞,優點可以清楚的識別出新特質的型別,缺點就是必須翻新久程式。

另一策略就是通過宣告一個或多個virtual function來區別class的宣告。其優點是透明化的將舊程式轉換過來,只要重新編譯好;缺點這是可能會將一個非必要的virtual function強迫匯入繼承體系的base class身上。目前C++的RTTI就是這個策略。

從編譯器角度來看,這個策略還是有其他優點:就是大量降低了額外的負擔。所有動態類的objects都維護了一個指標(vptr),指向vtbl。只要我們把該class的RTTI object地址放進virtual table,那麼額外的負擔就降低為:每一個class object 只要多花費一個指標。這指標只被設定一次,它是被編譯器靜態編譯的,而非執行期由class constructor設定(vptr才是這麼設定的)。

Type-Safe Dynamic Cast(保證安全的動態轉換)

dynamic_cast運算子可以在執行期決定真正的型別。如果downcast是安全的,這個運算子會傳回被被轉換過的指標,如果downcast不安全,會傳回0。

動態轉換的成本:對於一個基類指標轉化成子類的指標,必須通過class object型別描述符在執行期通過vptr取得的:

((type_info*)(pt->vptr[0]))->_type_descriptor;

type_info是C++ Standard所定義的型別描述器的class名稱,該class 中放著待索求的型別資訊。virtual table的第一個slot內含type_info object的地址;此type_info object與pt所指的class type有關。這兩個型別描述器被交給一個runtime library函式,比較之後告訴我們是否吻合。這個比static cast昂貴的多,但卻安全得多。

References並不是Pointers

dynamic_cast運算子也使用於reference身上。但是引用不能為0。對於向下轉換不成功,指標傳回0;引用是丟擲異常。

Typeid運算子

使用typeid運算子,就有可能以一個reference達到相同的執行期替代路線:

typeid運算子傳回一個const reference,型別為type_info。

對於typeid的測試等的運算子,是一個被overload的函式(typeid(a) == typeid(b)):

bool type_info::operator == (const type_info&) const;

type_info object定義如下:

class type_info
{
public:
		virtual ~type_info();
		bool operator==(const type_info&) const;
		bool operator!=(const type_info&) const;
		bool before(const type_info&) const;
		const char* name() const;
private:
	type_info(const type_info&);
	type_info& operator == (const type_info&);
};

雖然RTTI提供了type_info對於exception handing的支援來說必要的,但對於exception handing的完全支援而言,還不夠。如果再加上額外的一些type_info derived classes,就可以在exception發生時提供關於指標、函式、類等等的更詳細的資訊。

雖然RTTI適合於多型類,事實上type_info objects也適合於內建型別,以及fei多型的使用者的自定義型別。這對exception handing的支援是有必要的。例如:

int ex_errno;
...
throw ex_errno;

//int型別也有自己的type_info object :
int* ptr;
...
if(typeid(ptr) == typeid(int*))
...

//在程式中使用typeid(expression)
int ival;
...
typeid(ival)...;

//或使用typeid(type);
typeid(double)...;

上述使用typeid會傳回一個const type_info&,這是靜態取得的,非執行期。一般實現策略是在需要時才產生type_info object,而非程式一開頭就產生。