跳轉到內容

C++ 程式設計/RTTI

來自華夏公益教科書,自由的教科書

執行時型別資訊 (RTTI)

[編輯 | 編輯原始碼]

RTTI 指的是系統能夠報告物件的動態型別並在執行時(而不是在編譯時)提供有關該型別的資訊的能力,並且當一致地使用時,它可以成為一個強大的工具,可以簡化程式設計師在管理資源方面的工作。

考慮一下你已經瞭解的關於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和其他物件丟擲。

注意
C++98 標準要求在編譯單元中使用運算子typeid之前包含標頭檔案<typeinfo>。否則,程式將被認為是非法的。

在只需要類資訊的情況下,通常建議使用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 的誤用

[編輯 | 編輯原始碼]

RTTI 應該在 C++ 程式中謹慎使用。有很多原因。最重要的是,其他語言機制,如多型和模板,幾乎總是優於 RTTI。與所有事物一樣,存在例外,但關於 RTTI 的通常規則或多或少與goto語句相同。不要將其用作正確、更健壯設計的捷徑。只有在你有一個很好的理由這樣做,並且只有在你確切知道自己在做什麼時,才使用 RTTI。


  1. (由 std::type_info::name() 返回的確切字串依賴於編譯器)。
華夏公益教科書