C++中RTTI、dynamic_cast、typeid、虚函数表

一、RTTI是什么(Run TIme Type Identificantion)

Run TIme Type Identificantion:运行时类型识别;
通过运行时类型的识别,程序能够使用基类的指针或者引用来检查这些指针或者引用所指的对象的实际派生类型。

如之前博客虚函数写到A是B、C类的基类,A类定义一个指针指向B类对象。
https://blog.csdn.net/qq_43509546/article/details/110421909
RTTI可以看成是一种系统提供给我们的一种能力或功能,通过两个运算符来体现:
1、dynamic_cast运算符:返回指针或引用所指对象的实际类型
2、typeid运算符:返回指针或者引用所指对象的实际类型
注:要想让RTTI这两个运算符都能正常工作,那么基类中必须至少有一个虚函数,不然这两个运算符工作的结果就可能跟我们预测的不一样。 因为虚函数的存在,这两个运算符才会使用指针或者引用所绑定的对象的动态类型(new 的类型)

再回到问题中A是B、C的基类,A类指针指向B类对象,但是这个A类指针无法使用B类的成员函数。

#include <iostream>

using namespace std;

class A
{
public:
	A(int a = 1) :m_a(a) { /*cout << "A的构造函数" << endl;*/ }
	virtual ~A(){ /*cout << "A的析构" << endl;*/ }
	void matest(int n);
	void mcout()
	{
		cout << "acout" << endl;
	}
private:
	int m_a;
	
};

void A::matest(int n)
{
	this->m_a = n;
	cout << "m_a=" << m_a << endl;
}

class B:public A
{
public:
	B(int b = 2) :m_b(b) { /*cout << "B的构造函数" << endl;*/ }
	~B() { /*cout << "B的析构" << endl;*/ }
	void mbtest(int n);
	void mcout()
	{
		cout << "bcout" << endl;
	}
private:
	int m_b;
};

void B::mbtest(int n)
{
	m_b = n;
	cout << "m_b=" << m_b << endl;
}

class C :public A
{
public:
	C(int c = 3) :m_c(c) { /*cout << "C的构造函数" << endl;*/ }
	~C() { /*cout << "C的析构" << endl;*/ }
	void mctest(int n);
	void mcout()
	{
		cout << "ccout" << endl;
	}
private:
	int m_c;
};

void C::mctest(int n)
{
	m_c = n;
	cout << "m_c=" << m_c << endl;
}

int main()
{
	A *pa = new B(2);
	//pa->mbtest(1);//不能使用对象B的成员函数
	pa->mcout();
	cout << "----------------" << endl;
	B *p = (B *)pa;
	p->mbtest(1);

	return 0;
}
acout
----------------
m_b=1

以上通过以前学C语言的知识知道直接强制转换,这是明确知道pa指向的是B类,如果不知道pa指向的是什么类或者知道它是已知类其中一个该如何做

二、dynamic_cast运算符

dynamic_cast:如果该运算符转换成功,说明这个指针实际上是要转换到的那个类型。
这里需要提醒一下必须包含一个虚函数
对于指针

int main()
{
	A *pa = new B(2);
	//pa->mbtest(1);//不能使用对象B的成员函数
	pa->mcout();
	cout << "----------------" << endl;
	B *p = dynamic_cast<B *>(pa);//尝试转换成B类
	if (p != nullptr)
	{
		cout << "pa是一个B类型" << endl;
	}
	else
	{//转换失败
		cout << "pa不是一个B类型" << endl;
	}
	p->mbtest(1);

	return 0;
}
acout
----------------
pa是一个B类型
m_b=1

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

int main()
{
	A *pa = new B(2);
	//pa->mbtest(1);//不能使用对象B的成员函数
	pa->mcout();
	cout << "----------------" << endl;
	A &p = *pa;
	try 
	{
		B &bm = dynamic_cast<B &>(p);//尝试转换不成功则跳到catch里去,如果成功,流程继续往下走。
		//走到这里表示转换成功
		cout << "pa实际上是一个B类型" << endl;
		bm.mbtest(2);
	}
	catch (std::bad_cast)
	{
		cout << "pa实际不是B类型" << endl;
	}

	return 0;
}
acout
----------------
pa实际上是一个B类型
m_b=2

三、typeid运算符

typeid(类型[指针/引用]);也可能typeid(表达式);
typeid:拿到对象类型信息,typeid就会返回一个 “常量对象的引用”,这个常量对象是一个标准库类型 type_info(类/类型)。

int main()
{
	A *pa = new B(2);
	//pa->mbtest(1);//不能使用对象B的成员函数
	pa->mcout();
	A &p = *pa;
	cout << typeid(*pa).name() << endl;
	cout << typeid(p).name() << endl;
	char a[10] = { 5,1 };
	int b = 10;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(14.2).name() << endl;
	cout << typeid("asd").name() << endl;
	
	return 0;
}
acout
class B
class B
char [10]
int
double
char const [4]

typeid主要是为了比较两个指针是否指向同一种类型的对象
如下代码:两个指针定义的类型相同(A),不管他们new的是谁,typeid都相等

int main()
{
	A *p1 = new B(2);
	//pa->mbtest(1);//不能使用对象B的成员函数
	A *p2 = new C(2);
	if (typeid(p1) == typeid(p2))
	{
		cout << "p1和p2是同一种类型[看指针定义]" << endl;
	}

	return 0;
}
p1和p2是同一种类型[看指针定义]

但是有时我们是需要知道是指针指向的对象是什么类型,那么请看如下代码

int main()
{
	A *p1 = new B(2);
	//pa->mbtest(1);//不能使用对象B的成员函数
	B *p2 = new B(1);
	A *p3 = p2;
	if (typeid(*p1) == typeid(*p2))
	{
		cout << "p1和p2指向的对象类型相同" << endl;
	}
	if (typeid(*p2) == typeid(*p3))
	{
		cout << "p2和p3指向的对象类型相同" << endl;
	}
	
	return 0;
}
p1和p2指向的对象类型相同
p2和p3指向的对象类型相同

这个时候就是看new出来的对象,和定义时的对象类型没有关系,区别就在" * ",最后还是强调基类必须要有虚函数。

int main()
{
	A *p1 = new B(2);
	//pa->mbtest(1);//不能使用对象B的成员函数
	B *p2 = new B(1);
	A *p3 = p2;
	if (typeid(*p1) == typeid(B))
	{
		cout << "p1指向对象B类型" << endl;
	}

	return 0;
}

切记:只有当基类有虚函数,以上条件才成立。只有当基类有虚函数时,编译器才会对typeid()中的表达式求值, 否则typeid()返回的是表达式的静态类型(定义时的类型).既然是静态类型就不需要求值了,定义时就已知了。

四、type_info类

typeif就会返回一个 “常量对象的引用”,这个常量对象是一个标准库type_info(类/类型)
强调虚函数
1、.name:名字:返回一个c风格的字符串

int main()
{
	A *p1 = new B(2);
	const type_info &tp = typeid(*p1);
	cout << tp.name() << endl;

	return 0;
}
class B

2、== , !=

int main()
{
	A *p1 = new B(2);
	const type_info &tp = typeid(*p1);
	
	A *p2 = new B(2);
	const type_info &tp2 = typeid(*p2);
	if (tp == tp2)
	{
		cout << "tp和tp2类型相同" << endl;
	}
	A *p3 = new C(2);
	const type_info &tp3 = typeid(*p3);
	if (tp == tp3)
	{
		cout << "tp和tp3类型相同" << endl;
	}

	return 0;
}
tp和tp2类型相同

五、RTTI与虚函数表

C++中,如果类里含有虚函数。编译器就会对该类产生一个虚函数表。
虚函数表里有很多项,每一个项都是一个指针。每个指针指向的这个类里的各个虚函数的入口地址。
虚函数表项里,第一个表项很特殊,它指向的不是虚函数的入口地址,它指向的实际上是咱们这个类关磊的type_info对象,这就是为什么类中要包含虚函数typeif等才有效。

A *p1 = new B(2);
const type_info &tp = typeid(*p1);

p1对象里有一个我们看不见的指针,这个指针指向的是这个所在的类(B)里的虚函数表,系统通过这个虚函数表,把这个里面的第一项给弄出来,从而知道type_info对象,这样就能知道p1这个对象实际指向的是谁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值