天天看點

C++:關于C++的RTTI (運作階段類型識别)

RTTI 是運作階段類型識别(Runtime Type Identification)的簡稱;

RTTI隻适用于包含虛函數的類:

因為隻有對于這種類層次結構,才能将派生類對象的位址賦給基類指針。

一,RTTI的用途

假設有一個類層次結構,其中的類都是從同一個基類派生而來的,則可以讓基類指針指向其中任何一個類的對象。

這樣便可以調用這樣的函數:

在處理一些資訊後,選擇一個類,并建立這種類型的對象,然後傳回它的位址,而該位址可以被賦給一個基類指針。

問題:如何知道指針指向的是哪種對象呢?

首先,要确定其類型。

如是想調用類方法的正确挑版本。

        1)該函數是類層次結構中所有成員都擁有的虛函數,則并不真正需要知道其對象類型

        2)該函數是派生類對象的方法,而不是繼承而是來的。

       在這種情況下,隻有某些類型的對象可以使用該方法。也可能是出于調試的目的,想跟蹤生成對象的類型。

               這兩種情況下,RTTI提供了解決方案。

二,RTTI的工作原理

C++有3個支援RTTI的元素。

1)dynamic_cast該運算符将使一個指向基類的指針來生成一個指向派生類的指針,否則,該運算符傳回0--空指針。

2)typeid 該運算符傳回 一個指出對象的類型的值。

3)type_info該結構存儲了有關特定類型的資訊。

詳細介紹:

1,dynamic_cast運算符

dynamic_cast是最常用的RTTI元件,它不能回答“指針指向是哪類對象”,但能夠回答:“是否可以安全将對象的位址賦給特定類型的指針”;

class Grand{ // has virtual methods };
class Superb : public Grand { ... };
class Magnificent : public Superb { ... };

...

Grand * pg = new Grand;
Grand * ps = new Superb;
Grand * pm = Magnificent;

...

Magnificent * p1 = (Magnificent *) pm; // #1
Magnificent * p2 = (Magnificent *) pg; // #2
Superb * p3 = (Magnificent *) pm; // #3
           

#1:安全,因為它将Magnificent類型的指針指向Manificent類型的對象。

#2:不安全,因為它将基類對象Grand的位址賦給派生類(Magnificent)指針。這裡程式将希望基類對象有派生類的特性,而這通常是不可能的。

#3:安全,因為它将派生類對象的位址賦給基類指針。即公有派生確定Magnificent對象同時也是一個Superb對象(直接基類)與一個Grand對象(間接基類)。

    是以将它的位址賦給這三種類型的指針都是安全的。

通常想知道類型的原因在于:

知道類型後,就可以知道調用特定的方法是否安全。

要調用方法,并不一定要完全比對,而可以是定義了該方法的虛拟版本的基本類型。

使用的基本方法:

Superb * pm = dynamic_cast<Superb *>(pb);
           

這裡,指針pg能否被安全的轉換為Superb*?如果可以,運算符将傳回對象的位址,否則傳回一個0,即空指針。

例:

#include <iostream>
#include <cstdlib>
#include <ctime> // time(),srand(),rand()

using std::cout;
class Grand
{
private:
	int hold;
public:
	Grand(int h = 0) : hold(h) {}
	virtual void Speak() const
	{
		cout << "I am a grand class!\n";
	}
	virtual int Value() const
	{
		return hold;
	}
};

class Superb : public Grand
{
public:
	Superb(int h = 0) : Grand(h) {}
	void Speak() const
	{
		cout << "I am a superb class!\n";
	}
	virtual void Say() const
	{
		cout << "I hold the superb value of " << Value() << "!\n";
	}
};

class Magnificent : public Superb
{
private:
	char ch;
public:
	Magnificent(int h = 0, char c = 'A') : Superb(h), ch(c) {}
	void Speak() const 
	{
		cout << "I am a magnificent class!\n";
	}
	void Say() const 
	{
		cout << "I hold the character " << ch << " and the integer " << Value() << "!\n";
	}
};

Grand * GetOne();

int main()
{
	std::srand((unsigned)std::time(0));
	Grand * pg;
	Superb * ps;

	for (int i = 0; i < 5; ++i)
	{
		pg = GetOne();
		pg->Speak();
		if (ps = dynamic_cast<Superb *>(pg)) //無目的指派,有的通常用 ==
			ps->Say();
	}

	return 0;
}

Grand * GetOne()
{
	Grand * p = NULL;
	switch (std::rand() % 3)
	{
	case 0:
		{
			p = new Grand(std::rand() % 100);
		}
		break;
	case 1:
		{
			p = new Superb(std::rand() % 100);
		}
		break;
	case 2:
		{
			p = new Magnificent(std::rand() % 100,
				'A' + std::rand() % 26);
		}
		break;
	}
	return p;
}
           

輸出結果:

I am a grand class!
I am a superb class!
I hold the superb value of 29!
I am a magnificent class!
I hold the character Y and the integer 94!
I am a superb class!
I hold the superb value of 36!
I am a magnificent class!
I hold the character U and the integer 2!
請按任意鍵繼續. . .
           

上面為指針的用法,也可以用引用,但用法稍微不同:

因為沒有與空指針對應的引用值,是以無法使用特殊的引用值來訓示失敗,

當請求失敗時,dynamic_cast 将引發類型為 bad_cast 的異常,這種異常從 exception 類派生而來,在頭檔案typeinfo中定義。

引用用法:

#include <typeinfo> // for bad_cast
...
try{
	Superb & rs = dynamic_cast<Superb &>(rg); //假設rg為Grand對象的引用
	...
}
catch(bad_cast &)
{
	...
}
           

2,typeid 運算符和  type_info 類

typeid 運算符能夠确定兩個對象是否為同一類型,接受兩種參數:

1)類名;

2)結果為對象的表達式

typeid 運算符傳回一個對 type_info對象的引用;

type_info在頭檔案typeinfo中定義的,并定義了 == 與 != 運算符,以便對類型進行比較。

例:

typeid(Magnificent) == typeid(*pg) 
           
// 如果pg指向的是一個Magnificent 對象,則表達式為 true,否則為 false
           
// 但如果pg為一個空指針,則程式将引發 bad_typeid 異常。該異常也是從exception類中派生的,在頭檔案typeinfo中聲明的。
           

type_info 類的實作随廠商而異,但包含一個name()成員,該函數傳回一個随實作而異的字元串,通常為(但并非一定是)類的名稱;

例:

cout << "Now processing type " << typeid(*pg).name() << ".\n":	
           

例:與上例一樣,隻是改了main()函數

int main()
{
	std::srand((unsigned)std::time(0));
	Grand * pg;
	Superb * ps;

	for (int i = 0; i < 5; ++i)
	{
		pg = GetOne();
		cout << "Now processing type " << typeid(*pg).name() << ".\n"; // #1
		pg->Speak();
		if (ps = dynamic_cast<Superb *>(pg))
			ps->Say();
		if (typeid(Magnificent) == typeid(*pg)) // #2
			cout << "Yes, you are really magnificent.\n"
	}

	return 0;
}
           

3,放棄使用dynamic_cast ,隻使用typeid的方法

如上例的核心代碼:

Grand * pg;
	Superb * ps;

	for (int i = 0; i < 5; ++i)
	{
		pg = GetOne();
		pg->Speak();
		if (ps = dynamic_cast<Superb *>(pg))
			ps->Say();
	}
           

放棄使用dynamic_cast ,則

Grand * pg;
	Superb * ps;

	Magnificent * pm;

	for (int i = 0; i < 5; ++i)
	{
		pg = GetOne();
		if (typeid(Magnificent) == typeid(*pg))
		{
			pm = (Magnificent *)pg;
			pm->Speak();
			pm->Say();
		}
		else if (typeid(Superb) == typeid(*pg))
		{
			ps = (Superb *)ps;
			ps->Speak();
			ps->Say();
		}
		else
		{
			pg->Speak();
		}
	}
           

缺點:更長,更難看,不易修改。

繼續閱讀