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以及其他各种数值转换.