C++ 程式設計/RTTI
RTTI 指的是系統能夠報告物件的動態型別並在執行時(而不是在編譯時)提供有關該型別的資訊的能力,並且當一致地使用時,它可以成為一個強大的工具,可以簡化程式設計師在管理資源方面的工作。
dynamic_cast
[編輯 | 編輯原始碼]考慮一下你已經瞭解的關於dynamic_cast關鍵字的內容,假設我們有以下類層次結構
class Interface
{
public:
virtual void GenericOp() = 0;// pure virtual function
};
class SpecificClass : public Interface
{
public:
virtual void GenericOp();
virtual void SpecificOp();
};
假設我們還有一個型別為Interface*的指標,如下所示
Interface* ptr_interface;
假設出現一種情況,我們被迫假定但沒有保證該指標指向SpecificClass型別的物件,並且我們想要呼叫該類的成員SpecificOp()。要動態轉換為派生型別,我們可以使用dynamic_cast,如下所示
SpecificClass* ptr_specific = dynamic_cast<SpecificClass*>(ptr_interface);
if( ptr_specific ){
// our suspicions are confirmed -- it really was a SpecificClass
ptr_specific->SpecificOp();
}else{
// our suspicions were incorrect -- it is definitely not a SpecificClass.
// The ptr_interface points to an instance of some other child class of the base InterfaceClass.
ptr_interface->GenericOp();
};
使用dynamic_cast,程式將基類指標轉換為派生類指標,並允許呼叫派生類成員。但是要非常小心:如果你試圖轉換的指標不是正確型別,那麼dynamic_cast將返回一個空指標。
我們也可以將dynamic_cast與引用一起使用。
SpecificClass& ref_specific = dynamic_cast<SpecificClass&>(ref_interface);
它的工作原理與指標幾乎相同。但是,如果被轉換物件的實際型別不正確,那麼dynamic_cast不會返回空(不存在空引用)。相反,它會丟擲一個std::bad_cast異常。
- 語法
typeid( object );
typeid運算子用於在執行時確定物件的類。它返回對std::type_info物件的引用,該物件描述“物件”,並且一直存在到程式結束。如果“物件”是解除引用的空指標,那麼操作將丟擲一個std::bad_typeid異常。
std::bad_typeid類的物件派生自std::exception,由typeid和其他物件丟擲。
在只需要類資訊的情況下,通常建議使用typeid而不是dynamic_cast<class_type>,因為typeid在型別或非解引用值上應用是一個常數時間過程,而dynamic_cast必須在執行時遍歷其引數的類派生格。但是,你不應該依賴於確切的內容,例如由std::type_info::name()返回的內容,因為這對於編譯來說是特定於實現的。
通常,僅對指向多型類型別物件的指標或引用的解引用(即typeid(*ptr)或typeid(ref))使用typeid才有用(一個類至少有一個虛成員函式)。這是因為這些是與執行時型別資訊相關的唯一表達式。任何其他表示式的型別在編譯時都是靜態已知的。
- 示例
#include <iostream>
#include <typeinfo> //for 'typeid' to work
class Person {
public:
// ... Person members ...
virtual ~Person() {}
};
class Employee : public Person {
// ... Employee members ...
};
int main () {
Person person;
Employee employee;
Person *ptr = &employee;
// The string returned by typeid::name is implementation-defined
std::cout << typeid(person).name() << std::endl; // Person (statically known at compile-time)
std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-time)
std::cout << typeid(ptr).name() << std::endl; // Person * (statically known at compile-time)
std::cout << typeid(*ptr).name() << std::endl; // Employee (looked up dynamically at run-time
// because it is the dereference of a
// pointer to a polymorphic class)
}
輸出(確切的輸出因系統而異)
Person
Employee
Person*
Employee
在RTTI中,它在這種設定中使用
const std::type_info& info = typeid(object_expression);
有時我們需要知道物件的精確型別。typeid運算子返回對標準類std::type_info的引用,其中包含有關型別的資訊。該類提供了一些有用的成員,包括==和!=運算子。最有趣的方法可能是
const char* std::type_info::name() const;
這個成員函式返回一個指向 C 樣式字串的指標,其中包含物件型別的名稱。例如,使用我們之前示例中的類
const std::type_info &info = typeid(*ptr_interface);
std::cout << info.name() << std::endl;
這個程式會列印類似[1] SpecificClass的內容,因為這是指標ptr_interface的動態型別。
typeid實際上是一個運算子而不是一個函式,因為它也可以對型別進行操作
const std::type_info& info = typeid(type);
例如(有點迴圈地)
const std::type_info& info = typeid(std::type_info);
將給出描述type_info物件的type_info物件。後一種用法不是 RTTI,而是 CTTI(編譯時型別識別)。
RTTI 有一些侷限性。首先,RTTI 只能與多型型別一起使用。這意味著你的類必須至少有一個虛擬函式,無論是直接還是透過繼承。其次,由於儲存型別所需的額外資訊,一些編譯器需要特殊的開關來啟用 RTTI。
請注意,對指標的引用不會在 RTTI 下工作
void example( int*& refptrTest )
{
std::cout << "What type is *&refptrTest : " << typeid( refptrTest ).name() << std::endl;
}
將報告int*,因為typeid()不支援引用型別。
RTTI 應該在 C++ 程式中謹慎使用。有很多原因。最重要的是,其他語言機制,如多型和模板,幾乎總是優於 RTTI。與所有事物一樣,存在例外,但關於 RTTI 的通常規則或多或少與goto語句相同。不要將其用作正確、更健壯設計的捷徑。只有在你有一個很好的理由這樣做,並且只有在你確切知道自己在做什麼時,才使用 RTTI。
- ↑ (由 std::type_info::name() 返回的確切字串依賴於編譯器)。