執行時期型態資訊(RTTI)


在C++這種可以進行多型機制的語言中,您可以將基底類別指標指向衍生類別物件,這種指定通常在執行時期後發生,您並無法在編譯時期即得知指標所指向的物 件型態,而必須在執行時期取得物件的執行時期資訊。

RTTI 全名為Run-Time Type Information,也有人作Run-Time Type Identification,C++中用來取得指標或參考所實際代表的物件,您可以使用typeid()來取得物件於執行時期的資訊,要使用 typeid(),您必須包括<typeinfo>標頭檔,typeid()使用時傳入一個物件:
typeid(object);

typeid()會傳回一個type_info物件,其擁有幾個成員可以描述或進行物件的比較:
const char *name(); // 取得物件型態名稱
bool before(const type_info &ob);  // 當物件的名稱順序位於ob之前時,傳回true
bool operator==(const type_info &ob);  // 比較物件型態是否相同
bool operator!=(const type_info &ob); // 比較物件型態是否不同

==與!=運算子在這邊被重載為可以比較兩個物件的型態是否相同;typeid()也可以使用型態名稱作為引數,這通常是用來取得一個type-info 物件,並與一個物件作比較時使用:
typeid(type-name);

用範例來說明typeid()與其成員的使用方法與應用,下面這個程式只是使用name()取得物件的型態名稱:

#include <iostream> 
#include <typeinfo>
using namespace std;

class Base {
public:
virtual void foo() {
cout << "Base" << endl;
}
};

class Derived1 : public Base {
public:
void foo() {
cout << "Derived1" << endl;
}
};

class Derived2 : public Base {
public:
void foo() {
cout << "Derived2" << endl;
}
};

int main() {
Base *ptr; // 基底類別指標
Base base;
Derived1 derived1;
Derived2 derived2;

ptr = &base;
cout << "ptr 指向 "
<< typeid(*ptr).name()
<< endl;

ptr = &derived1;
cout << "ptr 指向 "
<< typeid(*ptr).name()
<< endl;

ptr = &derived2;
cout << "ptr 指向 "
<< typeid(*ptr).name()
<< endl;

return 0;
}

執行結果:

ptr 指向 4Base
ptr 指向 8Derived1
ptr 指向 8Derived2


您使用共同的基底類別指標來指向基底類別物件與衍生類別物件,雖然如此,還是利用typeid()取回的type-info物件仍可以得知物件的型態名 稱。

RTTI的使用時機之一,就是當您將物件以參考方式傳遞給函式時,函式的參數使用共同的基底類別指標或參考,但在函式中有必須操作衍生類別中的某個方法, 由於函式事先並不知道您傳入的物件型態名稱,所以您必須利用RTTI來進行判斷,下面的程式是個簡單的例子:

#include <iostream> 
#include <typeinfo>
using namespace std;

class Base {
public:
virtual void foo() = 0;
};

class Derived1 : public Base {
public:
void foo() {
cout << "Derived1" << endl;
}

void showOne() {
cout << "Yes! It's Derived1." << endl;
}
};

class Derived2 : public Base {
public:
void foo() {
cout << "Derived2" << endl;
}

void showTwo() {
cout << "Yes! It's Derived2." << endl;
}
};

void showWho(Base *base) {
base->foo();

if(typeid(*base) == typeid(Derived1)) {
Derived1 *derived1 = static_cast<Derived1*>(base);
derived1->showOne();
}
else if(typeid(*base) == typeid(Derived2)) {
Derived2 *derived2 = static_cast<Derived2*>(base);
derived2->showTwo();
}
}

int main() {
Derived1 derived1;
Derived2 derived2;

showWho(&derived1);
showWho(&derived2);

return 0;
}

執行結果:
Derived1
Yes! It's Derived1.
Derived2
Yes! It's Derived2.


傳統的C風格轉型語法也是可以使用的,例如:
Derived1 *derived1 = (Derived1*) base;

當然不建議使用這種方式強制轉型,事實上使用static_cast也不是很適合,C++為了支援RTTI還提供有dynamic_cast,這在下一個 主題中介紹。