C++的RTTI和cast运算符如何使用
本文小编为大家详细介绍“C++的RTTI和cast运算符如何使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“C++的RTTI和cast运算符如何使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
1. RTTI
RTTI是运行阶段类型识别(Running Type Identificarion)的简称。
如何知道指针指向的是哪种对象?
这是个很常见的问题,由于我们允许使用基类指针指向派生类,所以基类指针指向的对象可能是基类对象,也可能是派生类对象。但是我们需要知道对象种类,因为我们需要使用正确的类方法。
RTTI能解决上述问题。
1.1 dynamic_cast运算符
dynamic_cast
是最常用的RTTI组件,它不能回答"指针指向的是哪类对象",但是它能回答"是否可以安全的将对象的地址赋给特定类型指针"。什么是安全?
例如:A是基类,B是A的派生类,C是B的派生类。那么将BC对象的地址赋给A指针是安全的,而反向就是不安全的。因为公有继承满足
is-a
关系,指针和引用进行向上转换是安全的,向下转换就是不安全的。dynamic_cast
的用法是:dynamic_cast<Type*>(ptr)
或dynamic_cast<Type&>(ref)
可以看到的是,
dynamic_cast
是一种类型转换符,它支持指针间的转换,他将ptr
的类型转换成Type*
,如果这种转换是安全的,那会返回转换后的指针;如果不安全那就会返回空指针。对于引用的话,他将ref
的类型转换成Type&
,如果这种转换是安全的,那就返回转换后的引用;如果不安全那就会引发bad_cast
异常。总之,
dynamic_cast
类型转换运算符比C风格的类型转换要安全的多,而且它能够判断转换是否安全。//RTTI1.cpp
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
class Grand
{
protected:
int hold;
public:
Grand(int h=0):hold(h){};
virtual void Speak() const {cout<<"I am Grand class!
";}
};
class Superb:public Grand
{
public:
Superb(int h=0):Grand(h){}
virtual void Speak() const {cout<<"I am a superb class!
";}
virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}
};
class Magnificent:public Superb
{
private:
char ch;
public:
Magnificent(int h=0,char c='A'):Superb(h),ch(c){}
virtual void Speak() const{cout<<"I am a Magnificent class!
";}
virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}
};
Grand *GetOne();
int main()
{
srand(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();
}
}
delete pg;
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;
}
I am a Magnificent class!
the character: I
the value: 74
I am a superb class!
the value: 50
I am Grand class!
I am Grand class!
I am a Magnificent class!
the character: V
the value: 99
上面这个例子说明了重要的一点:尽可能使用虚函数,而只在必要时使用RTTI。
如果将
dynamic_cast
用于引用,当请求不安全的时候,会引发bad_cast
异常,这种异常时从exception
类派生而来的,它在头文件typeinfo
中定义。则我们可以使用如下方式处理异常:
#include<typeinfo>
...
try{
Superb & rs= dynamic_cast<Superb &>(rg);
....
}
catch(bad_cast &)
{
...
};
1.2 typeid运算符
type_info
类是在头文件typeinfo
中定义的一个类。type_info
类重载了==
和!=
运算符。typeid
是一个运算符,它返回一个对type_info
的引用,它可以接受2种参数:类名和对象。typeid
是真正回答了"指针指向的是哪类对象"。(实际上,
typeid
也可以用于其他类型,例如结构体,函数指针,各种类型,只要sizeof
能接受的,typeid
都能接受)用法:
typeid(Magnificent)==typeid(*pg)
如果
pg
是一个空指针,则会引发bad_typeid
异常。type_info
类中包含一个name()
接口,该接口返回字符串,一般情况下是类的名称。cout<<typeid(*pg).name()<<". "
例子:
//RTTI2.cpp
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<typeinfo>
using namespace std;
class Grand
{
protected:
int hold;
public:
Grand(int h=0):hold(h){};
virtual void Speak() const {cout<<"I am Grand class!
";}
};
class Superb:public Grand
{
public:
Superb(int h=0):Grand(h){}
virtual void Speak() const {cout<<"I am a superb class!
";}
virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}
};
class Magnificent:public Superb
{
private:
char ch;
public:
Magnificent(int h=0,char c='A'):Superb(h),ch(c){}
virtual void Speak() const{cout<<"I am a Magnificent class!
";}
virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}
};
Grand *GetOne();
int main()
{
srand(time(0));
Grand *pg;
Superb *ps;
for(int i=0;i<5;i++)
{
pg=GetOne();
cout<<"Now processing type "<<typeid(*pg).name()<<".
";
pg->Speak();
if(ps=dynamic_cast<Superb*>(pg))
{
ps->Say();
}
if(typeid(Magnificent)==typeid(*pg))
{
cout<<"Yes,you are really magnificent.
";
}
}
delete pg;
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;
}
Now processing type 6Superb.
I am a superb class!
the value: 64
Now processing type 11Magnificent.
I am a Magnificent class!
the character: Y
the value: 30
Yes,you are really magnificent.
Now processing type 5Grand.
I am Grand class!
Now processing type 11Magnificent.
I am a Magnificent class!
the character: C
the value: 3
Yes,you are really magnificent.
Now processing type 5Grand.
I am Grand class!
注意,你可能发现了
typeid
远比dynamic_cast
优秀的多,typeid
会直接告诉你类型是什么,而dynamic_cast
只告诉你是否可以类型转换。但是typeid
的效率比dynamic_cast
低很多,所以,请优先考虑dynamic_cast
和虚函数,如果不行才使用typeid
。2. cast运算符
C语言中的类型转换符太过随意,C++创始人认为,我们应该使用严格的类型转换,则C++提供了4个类型转换运算符:
dynamic_cast
const_cast
static_cast
reinterpret_cast
dynamic_cast
运算符已经介绍过了,它的用途是,使得类层次结构中进行向上转换,而不允许其他转换。const_cast
运算符,用于除去或增加 类型的const
或volatile
属性。它的语法是:
const_cast<type-name>(expression)
如果类型的其他方面也被修改,则上述类型转换就会出错,也就是说,除了
cv限定符
可以不同外,type_name
和expression
的类型必须相同。提供该运算符的目的是:有时候我们需要一个值:它在大多数情况下是常量,而有时候我们需要更改它。
看一个有趣的例子:
//cast运算符1.cpp
//cast运算符1.cpp
#include <iostream>
int main()
{
using std::cout;
using std::endl;
volatile const int a=100;
volatile const int & ra=a;//无法通过ra修改a
int &change_a=const_cast<int &>(ra);//可以通过change_a修改a;
change_a=255;
cout<<a<<endl;
}
上面最后这个
a
常量被修改成255,这是我们预期的结果。但是如果我把volatile
关键词去掉,那么a
的值还是100。其实是编译器在这里玩了个小聪明,const int a=100
,编译器认为a就不会发生改变了,所以就把a放在了寄存器中,因为访问寄存器要比访问内存单元快的多,每次都从寄存器中取数据,但是后来a在内存中发生变化后,寄存器中的数据没有发生变化,所以打印的是寄存器中的数据。解决这一问题,就使用volatile
关键词,那么对a的存取都是直接对内存做操作的。static_cast
运算符它的语法是:
static_cast<type-name>(expression)
仅当
type-name
可被隐式转换成expression
所属的类型或者expression
可以隐式转换成type-name
类型时,上述转换才合法,否则出现bad_cast
异常。例如,枚举值可以隐式转换成整型,那么整型就可以通过
static_cast
转换成枚举型。总之,
static_cast
只允许"相似"的类型间的转换,而不允许"差异很大"的类间的转换。reinterpret_cast
运算符:重新诠释类型,进行疯狂的类型转换它的语法时:
reinterpret_cast<type-name>(expression)
它可以进行类型间"不可思议"的互换,但是它不允许删除
const
,也不允许进行丧失数据的转换例如下面这两个例子:
#include<iostream>
int main()
{
using namespace std;
struct dat {short a; short b;};
long value =0xA224B118;
dat *pd=reinterpret_cast<dat*>(&value);
cout<<hex<<pd->a;
}
#include<iostream>
void fun()
{
std::cout<<"hello world!
";
}
int main()
{
using namespace std;
void (*foo)();
foo=fun;
int* p=reinterpret_cast<int*>(foo);
}
通常,
reinterpret_cast
转换符适用于底层编程技术,是不可移植的,因为不同系统可能使用不同大小,不同顺序来存储同一类型的数据。总之,C语言类型转换比
reinterpret_cast
还要"疯狂",非常不安全,所以推荐使用cast
运算符来实现类型转换。
相关文章