C++ Primer Plus 书之--C++ RTTI及类型转换

RTTI(Runtime Type Identification)

运行阶段类型识别, 就是运行阶段, 基类的指针能够知道指向的对象的具体类型:

C++有3个支持RTTI的元素

1.如果可能的话, dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针; 否则, 该运算符返回0--空指针.

2.typeid运算符返回一个指出对象的类型的值

3.type_info结构存储了有关特定类型的信息.

只能将RTTI用于包含虚函数的类层次结构, 因为只有对于这种类层次结构, 才应该将派生类对象的地址赋给基类指针.

1.dynamic_cast运算符

他可以返回是否可以安全地将对象的地址赋给特定类型的指针, 来看个例子:

假设有这样的类继承关系:

class Grand {
	// 有虚函数
};
class Superb: public Grand {...};
class Magnificent : public Superb {...};

接下来假设有下面的指针:

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

最后, 对于下面的类型转换:

// 转换是安全的
Magnificent * p1 = (Magnificent *) pm;
// 转换是不安全的, 因为pg是Grand对象的地址
Magnificent * p2 = (Magnificent *) pg;
// 转换是安全的
Superb * p3 = (Magnificent *) pm;

如果我们想知道类型转换是否安全, 可以使用dynamic_cast:

// pg指向一个对象
Superb * pm = dynamic_cast<Superb *>(pg);

上面这行代码表示: 指针pg的类型是否可被安全地转换为Superb *, 如果可以, 运算符将返回对象的地址, 否则返回一个空指针.

来看一个例子:

// rtti1.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>

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 " << std::endl; }
	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!" << std::endl;}
	virtual void Say() const {
		cout << "I hold the superb value of " << Value() << std::endl;
	}
};

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 " << std::endl; }
	void Say() const {
		cout << "I hold the character " << ch << " ant the integer " << Value() << std::endl;
	}
};

Grand * GetOne();

int main()
{
	std::srand(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;
	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;
}

程序运行结果为:

也可以将dynamic_cast用于引用, 其用法稍微不同: 没有与空指针对应的引用值, 因此无法使用特殊的引用值来指示失败. 当请求不正确时, dynamic_cast将引发类型为bad_cast的异常, 该异常是从exception类派生而来的, 它是在头文件typeinfo中定义的, 可以像下面这样使用该运算符:

// 为了使用bad_cast异常, 需要导入该头文件
#include <typeinfo>
...
try {
	Superb & rs = dynamic_cast<Superb &>(rg);
	...
} catch(bad_cast &) {
	...
}

typeid运算符和type_info类

typeid运算符使得能够确定两个对象是否为同种类型. 它与sizeof有些类似, 可以接受两种参数:

类名

结果为对象的表达式

typeid运算符返回一个对type_info对象的引用, 其中, type_info是在头文件typeinfo中定义的一个类. type_info类重载了==和!=运算符, 一边可以使用这些运算符来对类型进行比较, 例如:

typeid(Magnificent) == typeid(*pg)

如果pg指向的是一个Magnificent对象, 则上述表达式的结果为true, 否则为false;

如果pg是一个空指针, 程序将引发bad_typeid异常, 该异常是从exception类派生而来的, 在typeinfo中声明的.

// rtti2.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <typeinfo>

using namespace std;

class Grand
{
private:
	int hold;
public:
	Grand(int h = 0) : hold(h) {}
	virtual void Speak() const { cout << "I am a grand class " << endl; }
	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" << endl; }
	virtual void Say() const {
		cout << "I hold the superb value of " << Value() << endl;
	}
};

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

Grand * GetOne();

int main() 
{
	srand(time(0));
	Grand * pg;
	Superb * ps;
	for(int i = 0; i < 5; i++)
	{
		pg = GetOne();
		// typeid运算符返回一个type_info对象的引用
		cout << "Now processing type " << typeid(*pg).name() << endl;
		pg->Speak();
		if(ps = dynamic_cast<Superb *>(pg))
			ps->Say();
		if(typeid(Magnificent) == typeid(*pg))
			cout << "Yes, you're really magnificent" << endl;
	}
	return 0;
}

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

程序运行结果为:

 

const_cast运算符

用于执行只有一种用途的类型转换, 即改变值为const或volatile, 其语法与dynamic_cast运算符相同:

const_cast <type_name> (expression)

如果类型的其他方面也被修改, 则上述类型转换将出错. 也就是说, 除了const或volatile特征(有或无)可以不同外, type_name和expression的类型必须相同. 假设High和Low是两个类:

High bar;
const High * pbar= &bar;
...
// 合法的
High * pb = const_cast<High *>(pbar);
// 非法的
const Low * pl = const_cast<Low *> (pbar);

第二个转换是非法的, 因为它同时尝试将类型从const High * 改为 const Low *;

使用const_cast的原因是, 有时候可能需要这样一个值, 它在大多数时候是常量, 而有时有时可以修改的, 这种情况下, 可以将这个值声明为const, 并在需要修改它的时候, 使用const_cast(它删除const标签)

来举一个例子:

// constcast.cpp
#include <iostream>
using std::cout;
using std::endl;

void change(const int * pt, int n);

int main()
{
	int pop1 = 1000;
	const int pop2 = 2000;
	
	cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;
	change(&pop1, -103);
	change(&pop2, -103);
	cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;
	return 0;
}

void change(const int * pt, int n)
{
	int * pc;
	pc = const_cast<int *>(pt);
	*pc += n;
}

程序运行结果:

程序运行结果可以看出, 调用change()时, 修改了pop1, 但没有修改pop2. 在change()中, 指针被声明为const int *, 因此不能用来修改指向的int. 指针pc删除了const特征, 因此可以用来修改指向的值, 但晋档指向的值不是const时才可行, 因此pc可以用来修改pop1, 但不能用来修改pop2.

static_cast

运算符的语法和其他的类型转换运算符相同:

static_cast<type-name>(expression)

仅当type_name可被隐式转换为expression所属的类型或expression可被隐式转换为type_name所属的类型时, 上述转换才是合法的, 否则将出错.

假设High是Low的基类, Pond是一个无关的类, 则从High到Low的转换, 从Low到High的转换都是合法的, 而从Low到Pond的转换是不允许的:

High bar;
Low blow;
// 合法向上转换
High * pb = static_cast<High *> (&blow);
// 合法向下转换
Low * pl = static_cast<Low *>(&bar);
// 非法转换, Pond和他们无关
Pond * pmer = static_cast<Pond *> (&blow);

看第二种转换:从基类指针到派生类指针, 在不进行显示类型转换的情况下, 将无法进行. 但由于无需进行类型转换, 便可以进行另一个方向的类型转换, 因此使用static_cast来进行向下转换是合法的.

同理, 由于无需进行类型转换, 枚举值就可以被转换为整型, 所以可以用static_cast将整型转换为枚举值. 同样也可以使用static_cast将double转换为int, 将float转换为long以及其他各种数值转换.

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值