C++ 中的RTTI机制详解

文章目录

        • 前言
        • 一、typeid函数
        • 二、type_info类
        • 三、typeid函数的使用
            • ① 返回类型名称时需要注意一个问题,比如有如下代码:
            • ② 使用type_info类中重载的==和!=比较两个对象的类型是否相等
        • 四、dynamic_cast的内幕

前言

RTTI是 “Runtime Type Information” 的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法。RTTI并不是什么新的东西,很早就有了这个技术,但是,在实际应用中使用的比较少而已。而我这里就是对RTTI进行总结,今天我没有用到,并不代表这个东西没用。学无止境,先从typeid函数开始讲起。

一、typeid函数

typeid的主要作用就是让用户知道当前的变量是什么类型的,对于内置数据类型以及自定义数据类型都生效,比如以下代码:

#include 
#include 
using namespace std;

int main() {
     
	short s = 2;
	unsigned ui = 10;
	int i = 10;
	char ch = 'a';
	wchar_t wch = L'b';
	float f = 1.0f;
	double d = 2;

	cout << typeid(s).name() << endl;	// short
	cout << typeid(ui).name() << endl;	// unsigned int
	cout << typeid(i).name() << endl;	// int
	cout << typeid(ch).name() << endl;	// char
	cout << typeid(wch).name() << endl; // wchar_t
	cout << typeid(f).name() << endl;	// float
	cout << typeid(d).name() << endl;	// double

	return 0;
}

对于C++支持的内建类型,typeid能完全支持,我们通过调用typeid函数,我们就能知道变量的信息。对于我们自定义的结构体,类呢?

#include 
#include 
using namespace std;

class A {
     
public:
	void Print() {
      cout << "This is class A." << endl; }
};

class B : public A {
     
public:
	void Print() {
      cout << "This is class B." << endl; }
};

struct C {
     
	void Print() {
      cout << "This is struct C." << endl; }
};

int main() {
     
	A *pA1 = new A();
	A a2;

	cout << typeid(pA1).name() << endl; // class A *
	cout << typeid(a2).name() << endl;	// class A

	B *pB1 = new B();
	cout << typeid(pB1).name() << endl; // class B *

	C *pC1 = new C();
	C c2;

	cout << typeid(pC1).name() << endl; // struct C *
	cout << typeid(c2).name() << endl;	// struct C

	return 0;
}

是的,对于我们自定义的结构体和类,tpyeid都能支持。在上面的代码中,在调用完typeid之后,都会接着调用name()函数,可以看出typeid函数返回的是一个结构体或者类,然后,再调用这个返回的结构体或类的name成员函数;其实,typeid是一个返回类型为type_info类型的函数。 那么,我们就有必要对这个type_info类进行总结一下,毕竟它实际上存放着类型信息。

二、type_info类

在Visual Studio 2017中查看type_info类的定义,简化版本如下:

class type_info {
     
public:
	type_info(const type_info&) = delete;
	type_info& operator=(const type_info&) = delete;

	size_t hash_code() const noexcept;
	bool operator==(const type_info& _Other) const noexcept;
	bool operator!=(const type_info& _Other) const noexcept;
	bool before(const type_info& _Other) const noexcept;

	const char* name() const noexcept;
	const char* raw_name() const noexcept;

	virtual ~type_info() noexcept;

private:
	mutable __std_type_info_data _Data;
};

在 type_info 类中,拷贝构造函数和赋值运算符重载都被删除(C++11),同时也没有默认的构造函数;所以,我们没有办法创建type_info类的变量,例如 type_info A; 这样是错误的。那么typeid函数是如何返回一个type_info类的对象的引用的呢?我在这里不进行讨论,思路就是类的友元函数。

三、typeid函数的使用

typeid使用起来是非常简单的,常用的方式有以下两种:

  1. 使用type_info类中的name()函数返回对象的类型名称
  2. 使用type_info类中重载的==和!=比较两个对象的类型是否相等
① 返回类型名称时需要注意一个问题,比如有如下代码:
#include 
#include 
using namespace std;

class A {
     
public:
	void Print() {
      cout << "This is class A." << endl; }
};

class B : public A {
     
public:
	void Print() {
      cout << "This is class B." << endl; }
};

int main() {
     
	A *pA = new B();
	cout << typeid(pA).name() << endl;	// class A *
	cout << typeid(*pA).name() << endl; // class A
	return 0;
}

代码中使用了两次typeid,但是两次的参数是不一样的,输出结果也是不一样的。
当我指定为pA时,由于pA是一个A类型的指针,所以输出就为class A ;当我指定pA时,它表示的是pA所指向的对象的类型,所以输出的是class A。
所以需要区分typeid(*pA)和typeid(pA)的区别,它们两个不是同一个东西。
但是,这里又有问题了,明明pA实际指向的是B,为什么得到的却是class A呢?我们在看下一段代码:

#include 
#include 
using namespace std;

class A {
     
public:
	virtual void Print() {
      cout << "This is class A." << endl; }
};

class B : public A {
     
public:
	virtual void Print() {
      cout << "This is class B." << endl; }
};

int main() {
     
	A *pA = new B();
	cout << typeid(pA).name() << endl; // class A *
	cout << typeid(*pA).name() << endl; // class B
	return 0;
}

好了,我将Print函数变成了虚函数,输出结果就不一样了,这说明什么?这就是RTTI在捣鬼了
当类中不存在虚函数时,typeid是编译时期的事情,也就是静态类型,就如上面的cout<输出class A一样。
当类中存在虚函数时,typeid是运行时期的事情,也就是动态类型,就如上面的cout<输出class B一样。

关于这一点,我们在实际编程中,经常会出错,一定要谨记。

② 使用type_info类中重载的==和!=比较两个对象的类型是否相等

这个会经常用到,通常用于比较两个带有虚函数的类的对象是否相等,例如以下代码:

#include 
#include 
using namespace std;

class A {
     
public:
	virtual void Print() {
      cout << "This is class A." << endl; }
};

class B : public A {
     
public:
	void Print() {
      cout << "This is class B." << endl; }
};

class C : public A {
     
public:
	void Print() {
      cout << "This is class C." << endl; }
};

void Handle(A *a) {
     
	if (typeid(*a) == typeid(A)) {
     
		cout << "I am a A truly." << endl;
	}
	else if (typeid(*a) == typeid(B)) {
     
		cout << "I am a B truly." << endl;
	}
	else if (typeid(*a) == typeid(C)) {
     
		cout << "I am a C truly." << endl;
	}
	else {
     
		cout << "I am alone." << endl;
	}
}

int main() {
     
	A *pA = new B();
	Handle(pA);
	delete pA;
	pA = new C();
	Handle(pA);
	return 0;
}

这里输出的结果为:
I am a B truly.
I am a C truly.

四、dynamic_cast的内幕

#include 
#include 
using namespace std;

class A {
     
public:
	virtual void Print() {
      cout << "This is class A." << endl; }
};

class B {
     
public:
	virtual void Print() {
      cout << "This is class B." << endl; }
};

class C : public A, public B {
     
public:
	void Print() {
      cout << "This is class C." << endl; }
};

int main() {
     
	A *pA = new C;
	//C *pC = pA; // Wrong
	C *pC = dynamic_cast<C *>(pA);
	if (pC != NULL) {
     
		pC->Print();
	}
	delete pA;

	return 0;
}

这里输出为:This is class C

在上面代码中,如果我们直接将pA赋值给pC,这样编译器就会提示错误,而当我们加上了dynamic_cast之后,一切就ok了。那么dynamic_cast在后面干了什么呢?

dynamic_cast主要用于在多态的时候,它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转换类型,把基类指针(引用)转换为派生类指针(引用)。

当类中存在虚函数时,编译器就会在类的成员变量中添加一个指向虚函数表(vftable)的虚函数表指针(_vfptr)指针,编译器会在的 vftable(包括它自己的和从基类继承的)的第一个函数指针前面插入一个指向 _s_RTTICompleteObjectLocator 结构的指针(描述类信息的指针), GetCompleteObjectLocator 函数是用来从vftable 获得 _s_RTTICompleteObjectLocator 结构的,这个结构中包含了一个typeinfo结构体,结构体中包含了当前类的类型,因此,即使你将派生类的指针赋给基类的指针,你仍然可以利用上面的算法得到派生类的类型。

当我们进行dynamic_cast时,编译器会帮我们进行语法检查。
① 如果指针的静态类型和目标类型相同,那么就什么事情都不做。
② 否则,判断能否安全转换,如果不能,指针返回NULL,引用抛出bad_cast异常。

对于在typeid函数的使用中所示例的程序,我使用dynamic_cast进行更改,代码如下:

#include 
#include 
using namespace std;

class A {
     
public:
	virtual void Print() {
      cout << "This is class A." << endl; }
};

class B : public A {
     
public:
	void Print() {
      cout << "This is class B." << endl; }
};

class C : public A {
     
public:
	void Print() {
      cout << "This is class C." << endl; }
};

void Handle(A *a) {
     
	if (dynamic_cast<B*>(a)) {
     
		cout << "I am a B truly." << endl;
	}
	else if (dynamic_cast<C*>(a)) {
     
		cout << "I am a C truly." << endl;
	}
	else {
     
		cout << "I am alone." << endl;
	}
}

int main() {
     
	A *pA = new B();
	Handle(pA);
	delete pA;
	pA = new C();
	Handle(pA);

	return 0;
}

这里输出的结果为:
I am a B truly.
I am a C truly.
这个是使用dynamic_cast进行改写的版本。实际项目中,这种方法会使用的更多点。

你可能感兴趣的:(C++)