1. RTTI
1.1 RTTI定义
RTTI全称Run Time Type Identification, 运行时类型识别。通过运行时类型识别,程序能够使用基类的指针或者引用来检查这些指针或引用所指的对象的实际派生类型。
1.2 RTTI的实现
我们可以把RTTI看成系统赋予我们的一种能力,这种能力主要通过两个运算符实现。
(1)dynamic_cast运算符:能够将基类的指针或者引用安全的转换为子类的指针或引用。
(2)typeid运算符:返回指针或引用所指对象的实际类型.
注意:要让RTTI的两个运算符正常工作,基类中必须至少要有一个虚函数,不然这两个运算符的结果可能与预期不同。
2. dynamic_cast
dynamic_cast语法:dynamic_cast<需要转换的指针/引用类型>
dynamic_cast能够帮我们做安全检查,如果转换成功,则表示转化后的指针/引用是我们需要的类型。
#include <iostream>
using namespace std;
class Animal
{
virtual void func(){}; //使用RTTI,基类中必须至少存在一个虚函数,只有这样才能通过虚函数表找到该类型
};
class Dog : public Animal
{
public:
void eat()
{
cout << "狗吃骨头" << endl;
}
};
class Cat : public Animal
{
public:
void eat()
{
cout << "猫吃鱼" << endl;
}
};
int main() {
Animal *animal1 = new Dog; //父类指针指向子类(Dog)对象
//1. 指针的转换
Dog *dog = dynamic_cast<Dog *>(animal1);
if (dog != nullptr)
{
cout << "animal1指针被成功转换为Dog类型" << endl;
dog->eat(); // 狗吃骨头,此时animal1指针由Animal类型转换为Dog类型,可以调用Dog的函数
}
else //转换失败
{
cout << "animal1指针不是Dog类型" << endl;
}
//2. 引用的转换
Animal *animal2 = new Cat; //父类指针指向子类(Cat)对象
Animal &animal = *animal2; //animal为引用类型
// dynamic_cast在转换时,若转换失败会抛出bad_cast的异常
try {
Cat &cat = dynamic_cast<Cat &>(animal);
cat.eat(); //猫吃鱼,此时animal引用由Animal类型转换为Cat类型,可以调用Cat的函数
}
catch (bad_cast) {
cout << "animal引用没有被转换为Cat类型" << endl;
}
}
3. typeid
3.1 typeid的基本使用
int main() {
cout << typeid(3).name() << endl; //i
cout << typeid(3.14).name() << endl; //d
cout << typeid("aolaf").name() << endl; //A6_c
}
3.2 比较两个指针是否指向同一类型对象
注意:
- 比较指针所指向对象是否相同时需要加*,否则比较的是指针定义的类型。
#include <iostream>
using namespace std;
class Animal
{
virtual void func(){}; //使用RTTI,基类中必须至少存在一个虚函数,只有这样才能通过虚函数表找到该类型
};
class Dog : public Animal
{
};
class Cat : public Animal
{
};
int main() {
Animal *animal1 = new Dog;
Animal *animal2 = new Cat;
Animal *animal3 = animal1;
Dog *dog = new Dog;
if (typeid(animal1) == typeid(animal2)) //成立,此时比较的是animal1和animal2指针,只与定义类型有关,与new对象无关
{
cout << "animal1指针与animal2指针类型相同" << endl;
}
if (typeid(*animal1) == typeid(*animal3)) //成立, 均指向Dog对象
{
cout << "animal1指向对象与animal3相同" << endl;
}
if (typeid(*animal1) == typeid(*dog)) //成立,比较对象时取决于new,而非定义类型
{
cout << "animal1指向对象与dog相同" << endl;
}
}
- 只有基类里存在虚函数,编译器才会对typeid()表达式求值,如果某个类型不含有虚函数,则typeid()返回的是表达式的定义时的类型。
#include <iostream>
using namespace std;
class Animal
{
virtual void func(){}; //使用RTTI,基类中必须至少存在一个虚函数,只有这样才能通过虚函数表找到该类型
};
class Dog : public Animal
{
};
int main() {
Animal *animal1 = new Dog;
cout << typeid(*animal1).name() << endl; //Dog
}
#include <iostream>
using namespace std;
class Animal
{
// virtual void func(){}; //使用RTTI,基类中必须至少存在一个虚函数,只有这样才能通过虚函数表找到该类型
};
class Dog : public Animal
{
};
int main() {
Animal *animal1 = new Dog;
cout << typeid(*animal1).name() << endl; //Animal
}
4. type_info类
typeid会返回一个常量对象的引用,这个常量对象是一个标准库类型type_info
(a).name()返回一个c风格的字符串
#include <iostream>
using namespace std;
class Animal
{
virtual void func(){}; //使用RTTI,基类中必须至少存在一个虚函数,只有这样才能通过虚函数表找到该类型
};
class Dog : public Animal
{
};
int main() {
Animal *animal1 = new Dog; //父类指针指向子类(Dog)对象
const type_info &tp = typeid(*animal1); //Dog
cout << tp.name() << endl;
}
(b)== 和!=的使用
#include <iostream>
using namespace std;
class Animal
{
virtual void func(){}; //使用RTTI,基类中必须至少存在一个虚函数,只有这样才能通过虚函数表找到该类型
};
class Dog : public Animal
{
};
class Cat : public Animal
{
};
int main() {
Animal *animal1 = new Dog; //父类指针指向子类(Dog)对象
Animal *animal2 = new Cat;
Animal *animal3 = animal1;
const type_info &tp1 = typeid(*animal1);
const type_info &tp2 = typeid(*animal2);
const type_info &tp3 = typeid(*animal3);
if (tp1 == tp3)
cout << "tp1与tp3是同一类型" << endl; //tp1与tp3是同一类型
if (tp1 != tp2)
cout << "tp1与tp2不是同一类型" << endl; //tp1与tp2不是同一类型
}
5. RTTI与虚函数表
c++中,如果类里含有虚函数,编译器就会对该类生成一个虚函数表。
虚函数表里有很多项,每一项都是一个指针,每个指针指向的是这个类里的各个虚函数的入口地址。
虚函数表里,第一项指向的不是虚函数的入口地址,它指向的是这个类所关联的type_info对象。