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

C++ typeid 和虚函数详解

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

C++ typeid 和虚函数详解

typeid 和虚函数

前面咱们讲到 typeid 的操作返回值是 type_info 对象的引用,然后输出返回值的地址是相同的,测试代码如下:


#include <iostream>
#include <functional>
using namespace std;
class Base{
public:
    virtual 
	void test(){
		cout << "Base::test" << endl;
	}
};
class Derived : public Base{
public:
    void test(){
		cout << "Derived::test" << endl;
	}
	virtual 
	~Derived(){
		cout << "Derived::~Derived" << endl;
	}
};
int main()
{
	Base* pBase = new Base();
	Base* pBase2 = new Derived();
	Derived* pDerive = new Derived();
	//typeid(pBase2) 和 typeid(pDerive) 返回地址相同
    cout << "typeid(pBase2) = " << &typeid(*pBase2) <<  " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
    return 0;
}

output信息:

typeid(pBase2) = 0x55dd724c6d48 typeid(pDerive) = 0x55dd724c6d48

也就是说,0x55dd724c6d48 就是 Derived 类编译之后的类标识(type_info)数据信息!是否真的如此,咱们可以添加一下代码测试:


int main()
{
	Base* pBase = new Base();
	Base* pBase2 = new Derived();
	Derived* pDerive = new Derived();
	//typeid(pBase2) 和 typeid(pDerive) 返回地址相同
    cout << "typeid(pBase2) = " << &typeid(*pBase2) <<  " typeid(pDerive) = "<< &typeid(*pDerive) << endl;
	//class Base type_info 地址
	cout << "typeid(Base) = " << &typeid(Base)  << endl;
	//class Derive type_info 地址
	cout << "typeid(Derived) = " << &typeid(Derived)  << endl;
    //指针类型推导
    cout << "point ---- typeid(pBase2) = " << &typeid(pBase2) <<  " typeid(pDerive) = "<< &typeid(pDerive) << endl;
    return 0;
}

ouput信息:

typeid(pBase2) = 0x562309345d48 typeid(pDerive) = 0x562309345d48
typeid(Base) = 0x562309345d60
typeid(Derived) = 0x562309345d48
point ---- typeid(pBase2) = 0x562309345d28 typeid(pDerive) = 0x562309345d08

可以看到,Derived 类的 type_info 信息的地址就是 0x558a4dec7d48 !要注意的一点:直接对指针类型进行操作,并不能返回正确的原始类型。

好了嘛,那 typeid 到底是咋从虚函数表找到这个地址的呢?如果大家看过我之前的 深入理解new[]和delete[]_master-计算机科学专栏-CSDN博客 一文,应该就能够想到是不是C++编译器对虚函数表进行构造的过程中是不是也一样,做了地址偏移呢?

咱们看看上面代码的汇编信息:

通过查看汇编信息,我们得到以下结论:

虚函数表中确实存有typeinfo信息(第一个虚函数的地址偏移 -1 即是)typeinfo信息是区分指针类型是的(指针类型有前缀P,例如 P4Base、P7Derived)

然后,我们仔细观察四个 typeinfo 类(Derived*、 Base*、Derived、Base),每个typeinfo 类都有一个虚函数表,继承自 vtable for __cxxabiv1::******* ,后面的信息会不一样。这里对该信息做一下简单说明:

对于启用了 RTTI 的类来说会继承 __cxxabiv1 里的某个类所有的基础类(没有父类的类)都继承于_class_type_info所有的基础类指针都继承自 __pointer_type_info所有的单一继承类都继承自 __si_class_type_info所有的多继承类都继承自 __vmi_class_type_info

以typeinfo for Derived为例:

然后是指向存储类型名字的指针,
如果有继承关系,则最后是指向父类的 typeinfo 的记录。

所以,如果是正常调 typeinfo 基类(_class_type_info、__pointer_type_info、__si_class_type_info、__vmi_class_type_info)的方法,应该会动态调到 type_info 的继承类 (typeinfo for Derived*、typeinfo for Base*、typeinfo for Derived、typeinfo for Base)的方法。

但是,typeid 操作指针类型时并不是这样,说明C++编译器底层有特殊处理!

调试以下代码:


    cout << typeid(*pBase2).name();
    cout << typeid(*pDerive).name();
    cout << typeid(pBase2).name();
    cout << typeid(pDerive).name();

通过汇编信息,可以看到这里并没有做任何动态调用的逻辑,而是直接返回该指针类型的typeinfo信息,这也就解释了为什么 typeid 操作指针和操作对象的结果不一样!

那么我们在使用typeid时,如果要获取到真实对象类型,应该要将指针去掉!

为了验证我们前面的结论: 虚函数表中确实存有typeinfo信息(第一个虚函数的地址偏移 -1 即是),咱们可以直接通过指针的方式操作虚函数表!

测试代码如下:


#include <iostream>
#include <functional>
using namespace std;
class Base{
public:
    virtual 
	void test(){
		cout << "Base::test" << endl;
	}
};
class Derived : public Base{
public:
    void test(){
		cout << "Derived::test" << endl;
	}
	virtual 
	~Derived(){
		cout << "Derived::~Derived" << endl;
	}
};
typedef void (*FUNPTR)();
type_info* getTypeInfo(unsigned long ** vtbl){
	type_info* typeinfo = (type_info*)((unsigned long)vtbl[-1]);
	return typeinfo;
}
void visitVtbl(unsigned long ** vtbl, int count)
{
    cout << vtbl << endl;
    cout << "\t[-1]: " << (unsigned long)vtbl[-1] << endl;
    typedef void (*FUNPTR)();
    for (int i = 0; vtbl[i] && i < count; ++i)
    {
        cout << "\t[" << i << "]: " << vtbl[i] << " -> ";
        FUNPTR func = (FUNPTR)vtbl[i];
        func();
    }
}
int main()
{
	Base* pBase = new Base();
	Base* pBase2 = new Derived();
	Derived* pDerive = new Derived();
	//这里去遍历虚函数表
	visitVtbl((unsigned long **)*(unsigned long **)pBase2, 2);
	//获取虚函数表-1位置的typeinfo地址
	cout << "pDerive = " << getTypeInfo((unsigned long **)*(unsigned long **)pDerive) << " "
	<< getTypeInfo((unsigned long **)*(unsigned long **)pDerive)->name() << endl;
	//获取虚函数表-1位置的typeinfo地址
	cout << "pBase2 = " << getTypeInfo((unsigned long **)*(unsigned long **)pBase2) << " "
	<< getTypeInfo((unsigned long **)*(unsigned long **)pBase2)->name() << endl;
    return 0;
}

这里要注意的一点是,遍历虚函数表 visitVtbl 方法的第2个参数,自己控制不要越界,此外,还要注意调用的顺序,如果先调用了虚析构函数,会导致内存错误!

output信息:

0x5620022edd10
[-1]: 94695475567936
[0]: 0x5620022eb5fa -> Derived::test
[1]: 0x5620022eb636 -> Derived::~Derived
pDerive = 0x5620022edd40 7Derived
pBase2 = 0x5620022edd40 7Derived

通过直接访问虚函数表-1位置,我们可以看到输出的日志信息与我们前面的结论是一致的!也即是C++编译器给我们做了偏移操作(在-1的位置存储了type_info信息,实例化对象中的虚函数表地址是偏移之后的地址)。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!

免责声明:

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

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

C++ typeid 和虚函数详解

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

下载Word文档

猜你喜欢

C++ 函数重载和函数虚函数

答案: c++++ 中的函数重载和函数虚函数允许开发者创建具有相同名称但不同参数列表或行为的函数。详细描述:函数重载: 创建具有相同名称但不同参数列表的函数,用于在不同情况下使用具有相似功能的函数。函数虚函数: 在派生类中覆盖基类的函数,用
C++ 函数重载和函数虚函数
2024-04-15

详解C++虚函数表存储位置

相信大家知道虚表指针和虚函数存储的位置,但对于虚函数表的存储位置一时无法确定。本文就来和大家详细聊聊相关内容,希望对大家有所帮助
2023-05-14

C++虚函数和多态超详细分析

这篇文章主要介绍了C++多态的特性派生与虚函数与模板,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
2023-01-28

C++学习之虚函数表与多态详解

这篇文章主要为大家详细介绍了C++中虚函数表与多态的相关知识,文中的示例代码讲解详细,对我们学习C++有一定的帮助,感兴趣的小伙伴可以了解一下
2023-05-14

C++ 虚拟函数详解:解答多态机制之谜

c++++虚拟函数是实现多态性的关键,它允许派生类覆盖父类的行为:声明为虚函数,并在派生类中出现。调用时,编译器根据对象类型动态解析正确的函数版本。纯虚函数强制派生类提供自己的实现,以实现多态性。派生类覆盖基类的虚函数,实现不同的行为。C+
C++ 虚拟函数详解:解答多态机制之谜
2024-04-29

C++ 函数调试详解:如何调试虚函数中的问题?

虚函数调试方法:设置断点单步执行;使用 assert() 验证条件;利用调试器工具检查动态类型、函数栈和重新定义虚函数。C++ 函数调试详解:如何调试虚函数中的问题?引言在 C++ 中,虚函数是多态性机制的重要组成部分,然而调试虚函数中
C++ 函数调试详解:如何调试虚函数中的问题?
2024-05-02

C++lambda函数详解

小编可以明确告诉大家:lambda函数是C++11中最重要的,使用最广泛的,最具现代风格的内容,lambda函数的出现改变了C++编程的思维方式。所以快和小编学习一下C++11中lambda函数的使用吧
2023-02-13

c++虚函数和纯函数的区别是什么

C++中的虚函数和纯虚函数都是用来实现多态性的机制,但它们有一些不同之处。虚函数是在基类中声明的,可以在派生类中进行重写,它允许在运行时根据对象类型来调用相应的函数。虚函数可以被派生类重写,也可以选择不重写。如果派生类中没有重写虚函数,那
2023-10-26

详解C++虚函数中多态性的实现原理

C++是一种面向对象的编程语言,在C++中,虚函数是实现多态性的关键。本文就来探讨一下C++虚函数中多态性的实现原理及其在面向对象编程中的应用吧
2023-05-19

编程热搜

  • 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动态编译

目录