[C++ 从入门到精通] 16.RTTI、dynamic_cast、typeid、虚函数表

2023-12-20 11:13:38
  • 📢博客主页:https://loewen.blog.csdn.net
  • 📢欢迎点赞 👍 收藏 ?留言 📝 如有错误敬请指正!
  • 📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉
  • 📢现在的付出,都会是一种沉淀,只为让你成为更好的人?


一. RTTI是什么

RTTI(Run-Time Type Identification):通过运行时类型信息,程序能够使用基类的指针或引用检查这些指针或引用所指的对象的实际派生类型

前面我们提过父类指针可以new一个子类对象:

Human* phuman = new Man;
Human &a = *phuman;  // *phuman表示指针phuman所指向的对象

如果父类Human有多个子类,那么通过RTTI可以在系统运行时知道,父类Human指针phuman到底指向其哪个子类的对象。

RTTI我们可以把这个程序看成是一种系统提供给我们的一种能力,或者一种功能。这种功能或者能力是通过2个运算符来体现:

  1. dynamic_cast运算符:能够将基类的指针或者引用安全的转换为派生类的指针或者引用。
    应用:因为父类指针phuman无法调用子类Man对象成员,可以使用dynamic_cast将父类Human指针转换成子类Man指针。
  2. typeid运算符:返回指针或者引用所指对象的实际类型。

补充*:想让RTTI两个运算符能够过正常工作,基类中必须至少要有一个virtual虚函数,不然这两个运算符工作的结构就可能跟我们预期不一致。因为只有虚函数的存在,这两个运算符才会使用指针或者引用所绑定的对象的动态类型

class Human
{
public:
	virtual void print() { cout << "This is 人类" << endl; }
};

class Man :public Human 
{
public:
	virtual void print() { cout << "This is 男人" << endl; }
};

int main() 
{   
	Human * phuman = new Men;
	phuman->print();

	system("pause");
	return 0;
}

在这里插入图片描述

可以看到,父类和子类中都有virtual虚函数的前提下,如果父类指针指向子类对象,那么父类指针执行的对象就是子类对象里的virtual虚函数This is 男人

但是如果子类Man的成员函数print()不是虚函数(即父类中无同名虚函数),那么即使父类指针是指向的子类Man对象,正常情况下依然不能调用子类函数print()

问题每次想通过父类指针调用子类中的非虚函数,都需要在父类中加上同名虚函数吗?

显然这种方式太麻烦了,这时RTTI这种技术就派上用场了,通过dynamic_cast将父类指针转换成子类指针即可。


二. dynamic_cast类型(指针/引用)转换

优点

如果dynamic_cast运算符能够转换成功,说明这个指针实际上是要转换到的那个类型,即dynamic_cast运算符能够帮我们做安全检查。

2.1 C风格的强制类型转换
Human* phuman = new Man;
Man* p = (Man*)(phuman); // 用c语言风格的强制类型转换,硬把指针转换成Men*;
p->print();              // 能够正常的调用Men类的非虚成员函数print();
2.2 指针转换(常见用法)
class Human
{
public:
	virtual void print(); //使用dynamic_cast转换,父类中必须要有虚函数的存在
};

class Man :public Human
{
public:
	void printMan() { cout << "This is 男人" << endl; }
};
int main() 
{ 
	Human* phuman  = new Man;
	Man* pman = dynamic_cast<Man*>(phuman);
	if (pman != nullptr) {
	    // 可以在这里通过指针操作类Man里边的成员函数,成员变量都能够操作并且安全的操作 
	    pman->printMan();   
	} else {
	    // 转换失败:假设Human有子类Man和Women,指向子类Man的指针phuman被强制转换成子类Women的子针,则会转换失败
	    // Women* pman = dynamic_cast<Women*>(phuman);  这里就会转换失败
	    cout << "phuman不是一个Man类型" << endl;
	}
}

输出:
在这里插入图片描述
这里的printMan并不是虚函数,通过dynamic_cast将基类指针转换成了子类指针从而完成的对子类成员函数的调用。

注意:使用dynamic_cast转换,父类中必须要有虚函数的存在,否则会报错:

在这里插入图片描述

2.3 引用转换

如果用dynamic_cast转换失败,则系统会抛出一个std::bad_cast异常。

Human* phuman  = new Man;
Human &q = *phuman; // 这就是引用
try {
    Man& manbm = dynamic_cast<Man&>(q);
    // 转换成功
    cout << "phuman实际是一个Man类型" << endl;
    // 在这里操作类Men里边的成员函数,成员变量都能够操作并且安全的操作
    manbm.printMan();
} catch(std::bad_cast) {
    // 转换失败
    cout << "phuman不是一个Man类型" << endl;
}

三. typeid运算符

typeid运算符是用来获取一个指针/引用/表达式的类型信息,拿到对象类型信息,typeid就会返回一个常量对象的引用,这个常量对象是一个标准库类型type_info(类/类类型)

1.定义

typeid(类型或表达式);  //类型可以是指针或引用

2.包含文件typeid是一个标准库typeinfo中的函数,所以需要添加如下代码:

#include <typeinfo>

3.代码举例

Human* phuman = new Man;
Human& q = *phuman;
cout << typeid(*phuman).name() << endl; // 通过typeid确认phuman指向什么类型 —— class Man;
cout << typeid(q).name() << endl;       // 通过typeid确认q指向什么类型 —— class Man
char a[10] = {5, 1};
int b = 666;
cout << typeid(a).name() << endl;       // char[10]
cout << tyepid(b).name() << endl;       // int
cout << tyepid(19.6).name() << endl;    // double
cout << tyepid("asd").name() << endl;   // char const[4]

4.目的typeid主要是为了比较两个指针是否指向同一种类型的对象

1)两个指针定义的类型相同(Human),不管他们new的是啥,typeid返回的对象类型都相等

该例不太符合我们的期盼和要求:

Human* phuman = new Man;
Human* phuman2 = new Women;
if (typeid(phuman) == typeid(phuman2)) { // 成立
    cout << "phuman和phuman2是同一种类型[看指针定义]" << endl;
}

2)比较对象时,看的是new出来的是哪个对象或者该指针指向的是哪个对象,和定义该指针时定义的类型没关系。

Human* phuman = new Man;    //实际指向Man对象
Human* phuman3 = phuman;    //实际指向Man对象
if (typeid(*phuman) == typeid(Man)) { // 成立
    cout << "phuman指向Man" << endl;
}
if (typeid(*phuman) == typeid(*phuman3)) { // 成立
    cout << "phuman和phuman3指向的对象类型相同" << endl;
}
if (typeid(*phuman) == typeid(Human)) { //父类Human中没有虚函数时,该条件成立
    cout << "phuman和phuman3指向的对象类型相同" << endl;
}

注意1)和2)的写法,2)的写法(带*)才是满足我们期望要求的

切记: 基类必须要有虚函数,只有这样,编译器才会对typeid()中的表达式求值。否则如果某个类型不含有虚函数,则typeid()返回的是表达式的静态类型(定义时的类型),既然是定义的类型,编译器就不需要对表达式求值,也能知道表达式的静态类型。


四. type_info类

1.定义

const type_info& type_info变量 = typeid(类型或表达式);

2.包含文件:typeid是一个标准库typeinfo中的函数,所以需要添加如下代码:

#include <typeinfo>

3.用法

1)获取所指对象的名字.name,返回一个C风格字符串

Human* phuman = new Man;
const type_info& tp = typeid(*phuman);
cout << tp.name() << endl; // class Men

2)==、!=

Human* phuman2 = new Man;
const type_info& tp2 = typeid(*phuman2);
if(tp == tp2) {    // 成立
    cout << "tp和tp2类型相同" << endl;
}
Human* phuman3 = new Women;
const type_info& tp3 = typeid(*phuman3);
if(tp == tp3) {    // 不成立
    cout << "tp和tp3类型相同" << endl;
}

五. RTTI与虚函数表

C++中,如果类里含有虚函数。编译器就会对该类产生一个虚函数表

虚函数里有很多项,每一个项都是一个指针。每个指针指向的是这个类里的各个虚函数的入口地址(方便以后进行调用)。

虚函数表项里,第一个表项很特殊,它指向的不是虚函数的入口地址,它指向的实际上是咱们这个类所关联的type_info对象。

Human* phuman = new Man;
const type_info& tp = typeid(*phuman);  //phuman指向Man这个对象

phuman对象里有一个我们看不见的指针,这个指针指向是这个对象所在的类Man里的虚函数表。


下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。

文章来源:https://blog.csdn.net/weixin_43197380/article/details/135076877
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。