我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++对象模型之RTTI的实现原理是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++对象模型之RTTI的实现原理是什么

本篇内容介绍了“C++对象模型之RTTI的实现原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

RTTI是Runtime Type  Identification的缩写,意思是运行时类型识别。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。但是现在RTTI的类型识别已经不限于此了,它还能通过typeid操作符识别出所有的基本类型(int,指针等)的变量对应的类型。

C++通过以下的两个操作提供RTTI:

  • typeid运算符,该运算符返回其表达式或类型名的实际类型。

  • dynamic_cast运算符,该运算符将基类的指针或引用安全地转换为派生类类型的指针或引用。

下面分别详细地说明这两个操作的实现方式。

注所有的测试代码的测试环境均为:32位Ubuntu 14.04 g++ 4.8.2,若在不同的环境中进行测试,结果可能有不同。

1、typeid运算符

typeid运算符,后接一个类型名或一个表达式,该运算符返回一个类型为std::tpeinf的对象的const引用。type_info是std中的一个类,它用于记录与类型相关的信息。类type_info的定义大概如下:

class type_info {     public:         virtual ~type_info();         bool operator==(const type_info&)const;         bool operator!=(const type_info&)const;         bool before(const type_info&)const;         const char* name()const;     private:         type_info(const type_info&);         type_info& operator=(const type_info&);                 // data members };

至于data  members部分,不同的编译器会有所不同,但是都必须提供最小量的信息是class的真实名称和在type_info对象之间的某些排序算法(通过before()成员函数提供),以及某些形式的描述器,用来表示显式的类的类型和该类的任何子类型。

从上面的定义也可以看到,type_info提供了两个对象的相等比较操作,但是用户并不能自己定义一个type_info的对象,而只能通过typeid运算符返回一个对象的const引用来使用type_info的对象。因为其只声明了一个构造函数(复制构造函数)且为private,所以编译器不会合成任何的构造函数,而且赋值操作运行符也为private。这两个操作就完全禁止了用户对type_info对象的定义和复制操作,用户只能通过指向type_info的对象的指针或引用来使用该类。

下面说说,typeid对静态类型的表达式和动态类型的表达式的处理和实现。

1)typeid识别静态类型

当typeid中的操作数是如下情况之一时,typeid运算符指出操作数的静态类型,即编译时的类型。

  • 类型名

  • 一个基本类型的变量

  • 一个具体的对象

  • 一个指向不含有virtual函数的类对象的指针的解引用

  • 一个指向不含有virtual函数的类对象的引用

静态类型在程序的运行过程中并不会改变,所以并不需要在程序运行时计算类型,在编译时就能根据操作数的静态类型,推导出其类型信息。例如如下的代码片断,typeid中的操作数均为静态类型:

class X  {  ...... // 具有virtual函数 };  class XX : public X  { ...... // 具有virtual函数};  class Y  { ...... // 没有virtual函数};    int main() {     int n = 0;     XX xx;     Y y;     Y *py = &y;       // int和XX都是类型名     cout << typeid(int).name() << endl;     cout << typeid(XX).name() << endl;     // n为基本变量     cout << typeid(n).name() << endl;     // xx所属的类虽然存在virtual,但是xx为一个具体的对象     cout << typeid(xx).name() << endl;     // py为一个指针,属于基本类型     cout << typeid(py).name() << endl;     // py指向的Y的对象,但是类Y不存在virtual函数     cout << typeid(*py).name() << endl;     return 0; }

2)typeid识别多态类型

当typeid中的操作数是如下情况之一时,typeid运算符需要在程序运行时计算类型,因为其其操作数的类型在编译时期是不能被确定的。

  • 一个指向不含有virtual函数的类对象的指针的解引用

  • 一个指向不含有virtual函数的类对象的引用

多态的类型是可以在运行过程中被改变的,例如,一个基类的指针,在程序运行的过程中,它可以指向一个基类对象,也可以指向该基类的派生类的对象,而typeid运算符需要在运行过程中识别出该基类指针所指向的对象的实际类型,这就需要typeid运算符在运行过程中计算其指向的对象的实际类型。例如对于以下的类定义:

class X {     public:         X()         {             mX = 101;         }         virtual void vfunc()         {             cout << "X::vfunc()" << endl;         }     private:         int mX; }; class XX : public X {     public:         XX():             X()         {             mXX = 1001;         }         virtual void vfunc()         {             cout << "XX::vfunc()" << endl;         }     private:         int mXX; };

使用如下的代码进行测试:

void printTypeInfo(const X *px) {     cout << "typeid(px) -> " << typeid(px).name() << endl;     cout << "typeid(*px) -> " << typeid(*px).name() << endl; } int main() {     X x;     XX xx;     printTypeInfo(&x);     printTypeInfo(&xx);     return 0; }

其输出如下:

C++对象模型之RTTI的实现原理是什么

从输出的结果可以看出,无论printTypeInfo函数中指针px指向的对象是基类X的对象,还是指向派生类XX的对象,typeid运行返回的px的类型信息都是相同的,因为px为一个静态类型,其类型名均为PX1X。但是typeid运算符却能正确地计算出了px指向的对象的实际类型。(注:由于C++为了保证每一个类在程序中都有一个独一无二的类名,所以会对类名通过一定的规则进行改写,所以在这里显示的类名跟我们定义的有一些不一样,如类XX的类名,被改写成了2XX。)

那么问题来了,typeid是如何计算这个类型信息的呢?下面将重点说明这个问题。

多态类型是通过在类中声明一个或多个virtual函数来区分的。因为在C++中,一个具备多态性质的类,正是内含直接声明或继承而来的virtual函数。多态类的对象的类型信息保存在虚函数表的索引的-1的项中,该项是一个type_info对象的地址,该type_info对象保存着该对象对应的类型信息,每个类都对应着一个type_info对象。下面就对这一说法进行验证。

使用如以的代码,对上述的类X和类XX的对象的内存布局进行测试:

typedef void (*FuncPtr)(); int main() {     XX xx;     FuncPtr func;     char *p = (char*)&xx;     // 获得虚函数表的地址     int **vtbl = (int**)*(int**)p;     // 输出虚函数表的地址,即vptr的值     cout << vtbl << endl;     // 获得type_info对象的指针,并调用其name成员函数     cout << "\t[-1]: " << (vtbl[-1]) << " -> "         << ((type_info*)(vtbl[-1]))->name() << endl;     // 调用第一个virtual函数     cout << "\t[0]: " << vtbl[0] << " -> ";     func = (FuncPtr)vtbl[0];     func();     // 输出基类的成员变量的值     p += sizeof(int**);     cout << *(int*)p << endl;     // 输出派生类的成员变量的值     p += sizeof(int);     cout << *(int*)p << endl;     return 0; }

测试代码,对类XX的对象的内存布局进行测试,其输出结果如下:

C++对象模型之RTTI的实现原理是什么

从运行结果可以看到,利用虚函数表的-1的项的地址转换成一个type_info的指针类型,并调用name成员函数的输出为2XX,其输出与前面的测试代码中利用typeid的输出一致。从而可以知道,关于多态类型的计算是通过基类指针或引用指向的对象(子对象)的虚函数表获得的。

从运行的结果可以知道,类XX的对象的内存布局如下:

C++对象模型之RTTI的实现原理是什么

对于以下的代码片断:

typeid(*px).name()

可能被转换成如下的C++伪代码,用于计算实际对象的类型:

(*(type_info*)px->vptr[-1]).name();

在多重继承和虚拟继承的情况下,一个类有n(n>1)个虚函数表,该类的对象也有n个vptr,分别指向这些虚函数表,但是一个类的所有的虚函数表的索引为-1的项的值(type_info对象的地址)都是相等的,即它们都指向同一个type_info对象,这样就实现了无论使用了哪一个基类的指针或引用指向其派生类的对象,都能通过相应的虚函数表获取到相同的type_info对象,从而得到相同的类型信息。

3)typeid的识别错误的情况

从第2)节可以看到,typeid对于多态类型是通过虚函数表来计算的,若一个基类的指针指向了一个派生类,而该派生类并不存在virtual函数会出现什么情况呢?

例如,把第2)节中的X和XX类中的virtual函数全部去掉,改成以下的代码:

class X {     public:         X()         {             mX = 101;         }     private:         int mX; };   class XX : public X {     public:         XX():             X()         {             mXX = 1001;         }     private:         int mXX; };

测试代码不变,如下:

void printTypeInfo(const X *px) {     cout << "typeid(px) -> " << typeid(px).name() << endl;     cout << "typeid(*px) -> " << typeid(*px).name() << endl; } int main() {     X x;     XX xx;       printTypeInfo(&x);     printTypeInfo(&xx); // 注释1       return 0; }

其输出如下:

C++对象模型之RTTI的实现原理是什么

从输出的结果可以看到,对于注释1的函数调用,虽然函数中基类(X)的指针px指向一个派生类对象(XX类的对象xx),但是typeid却并不没有像第2)节那样能正确地通过指针px计算出其所指对象的实际类型。

其原因在于类XX和类X都没有一个virtual函数,所以类XX和类X并不表现出多态类的性质。所以对类的指针的解引用符合第1)节中所说的静态类型,所以其类型信息是在编译时就已经确定的,并不需要在程序运行的过程中运行计算,所以其输出的类型均为1X而没有输出1XX。更进一步说,是因为类X和类XX都不存在virtual函数,所以类X和XX都不存在虚函数表,所以也就没有空间存储跟类X和XX类型有关的type_info对象的地址。

然而在C++中即使一个类不具有多态的性质,仍然允许把一个派生类的指针赋值给一个基类的指针,所以这个错误比较隐晦。

2、dynamic_cast运算符

把一个基类类型的指针或引用转换至继承架构的末端某一个派生类类型的指针或引用被称为向下转型(downcast)。dynamic_cast运算符的作用是安全而有效地进行向下转型。

把一个派生类的指针或引用转换成其基类的指针或引用总是安全的,因为通过分析对象的内存布局可以知道,派生类的对象中必然存在基类的子对象,所以通过基类的指针或引用对派生类对象进行的所有基类的操作都是合法和安全的。而向下转型有潜在的危险性,因为基类的指针可以指向基类对象或其任何派生类的对象,而该对象并不一定是向下转型的类型的对象。所以向下转型遏制了类型系统的作用,转换后对指针或引用的使用可能会引发错误的解释或腐蚀程序内存等错误。

例如对于以下的类定义:

class X {     public:         X()         {             mX = 101;         }         virtual ~X()         {         }     private:         int mX; };   class XX : public X {     public:         XX():             X()         {             mXX = 1001;         }         virtual ~XX()         {         }     private:         int mXX; };   class YX : public X {     public:         YX()         {             mYX = 1002;         }         virtual ~YX()         {         }     private:         int mYX; };

使用如下的测试代码,其中的类型转换均为向下转型:

int main(){ X x; XX xx; YX yx; X *px = &xx; cout << px << endl; XX *pxx = dynamic_cast<XX*>(px); // 转换1 cout << pxx << endl; YX *pyx = dynamic_cast<YX*>(px); // 转换2 cout << pyx << endl; pyx = (YX*)px; // 转换3 cout << pyx << endl; pyx = static_cast<YX*>(px); // 转换4 cout << pyx << endl; return 0;}

其运行结果如下:

C++对象模型之RTTI的实现原理是什么

运行结果分析

px是一个基类(X)的指针,但是它指向了派生类XX的一个对象。在转换1中,转换成功,因为px指向的对象确实为XX的对象。在转换2中,转换失败,因为px指向的对象并不是一个YX对象,此时dymanic_cast返回NULL。转换3为C风格的类型转换而转换4使用的是C++中的静态类型转换,它们均能成功转换,但是这个对象实际上并不是一个YX的对象,所以在转换3和转换4中,若继续通过指针使用该对象必然会导致错误,所以这个转换是不安全的。

从上述的结果可以看出在向下转型中,只有dynamic_case才能实现安全的向下转型。那么dynamic_case是如何实现的呢?有了上面typeid和虚函数表的知识后,这个问题并不难解释了,以转换1为例。

  • 计算指针或引用变量所指的对象的虚函数表的type_info信息,如下:

*(type_info*)px->vptr[-1]
  • 静态推导向下转型的目标类型的type_info信息,即获取类XX的type_info信息

  • 比较1)和2)中获取到的type_info信息,若2)中的类型信息与1)中的类型信息相等或是其基类类型,则返回相应的对象或子对象的地址,否则返回NULL。

引用的情况与指针稍有不同,失败时并不是返回NULL,而是抛出一个bad_cast异常,因为引用不能参考NULL。

“C++对象模型之RTTI的实现原理是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++对象模型之RTTI的实现原理是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

golang对象池的实现原理是什么

Golang对象池是一种用于重复利用对象的机制,以避免频繁的创建和销毁对象的开销。它通过预先创建一定数量的对象,并在需要时从池中获取对象,使用完毕后再将对象放回池中,以供后续的使用。Golang对象池的实现原理主要包括以下几个步骤:初始化
2023-10-27

Java面向对象之多态的原理是什么与怎么实现

本文小编为大家详细介绍“Java面向对象之多态的原理是什么与怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java面向对象之多态的原理是什么与怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。何为多
2023-06-30

GO并发模型的实现原理是什么

这篇文章主要介绍了GO并发模型的实现原理是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇GO并发模型的实现原理是什么文章都会有所收获,下面我们一起来看看吧。前言请记住下面这句话:DO NOT COMMUNI
2023-06-30

C++ 函数模板的底层实现原理是什么?

c++++ 函数模板通过部分特化根据不同的类型生成不同的函数代码,优化效率。每次调用模板时,编译器会实例化函数并生成特定于模板参数的特化代码。函数模板提供代码重用、灵活性和性能优化等优势。C++ 函数模板的底层实现原理函数模板是 C++
C++ 函数模板的底层实现原理是什么?
2024-04-24

C++继承的实现原理是什么

C++继承的实现原理是通过派生类继承基类的成员和方法。当一个派生类继承一个基类时,派生类会拥有基类的所有成员变量和成员函数。在内存中,派生类的对象会包含基类的子对象。C++使用了两种类型的继承:公有继承和私有继承。公有继承表示派生类可以访
2023-10-26

c++虚函数的实现原理是什么

C++虚函数的实现原理是通过虚函数表(vtable)和虚函数指针(vptr)来实现的。当一个类中声明了虚函数时,编译器会在该类的对象中添加一个指向虚函数表的虚函数指针(vptr)。虚函数表是一个存储类的虚函数地址的表格,每个类对象都有一个
c++虚函数的实现原理是什么
2024-02-29

C++中继承的实现原理是什么

C++中继承的实现原理是通过创建一个新的类(派生类)来继承已有的类(基类)的属性和方法。派生类可以访问基类中的非私有成员,并且可以扩展或修改基类的功能。在C++中,派生类可以使用关键字class或struct来声明,并在类声明中使用关键字
C++中继承的实现原理是什么
2024-02-29

C++单例模式实例化一个对象不全部使用static的原因是什么

今天小编给大家分享一下C++单例模式实例化一个对象不全部使用static的原因是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一
2023-06-30

C++与Lua实现交互的原理是什么

本篇文章给大家分享的是有关C++与Lua实现交互的原理是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。具体步骤:1,找到cocos自带的绑定工具脚本文件genbinding
2023-06-06

c语言解释器的实现原理是什么

C语言解释器的实现原理是将C语言源代码转换为可执行的机器代码并执行。下面是C语言解释器的基本实现原理:1. 词法分析:将源代码分解为一系列的单词(token),如关键字、标识符、运算符和常量等。2. 语法分析:根据C语言的语法规则,将词法分
2023-08-08

Python虚拟机中整型的实现原理是什么

这篇文章主要介绍“Python虚拟机中整型的实现原理是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python虚拟机中整型的实现原理是什么”文章能帮助大家解决问题。数据结构在 cpython
2023-07-05

Android中实现观察者模式的原理是什么

这篇文章将为大家详细讲解有关Android中实现观察者模式的原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。观察者模式:A类中定义一个被观察者画家package com.exampl
2023-05-31

Golang基础学习之map的实现原理是什么

这篇文章主要讲解了“Golang基础学习之map的实现原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang基础学习之map的实现原理是什么”吧!0. 简介哈希表是常见的数据结
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录