C++虚函数及虚函数表解析

  核心提示:虚函数必需是类的非静态成员函数(且非构造函数), 其拜访权限是public(可以定义为privateorproteceted, 没有意义。 在基类的类定义中定义虚函数的一般方式

  虚函数的定义:

  虚函数必需是类的非静态成员函数(且非构造函数), 其拜访权限是public(可以定义为privateorproteceted, 但是关于多态来说, ), 也就是在程序的运行阶段静态地选择合适的成员函数,

  可以在基类的派生类中对虚函数重新定义(方式也是:virtual函数前往值类型虚函数名(形参表){函数体}), 以完成一致的接口, 不同定义过程。 如果在派生类中没有对虚函数重新定义, 当程序发现虚函数名前的关键字virtual后, 会自动将其作为静态联编处理,

  完成静态联编需求三个条件:

  1、必需把需求静态联编的行为定义为类的公共属性的虚函数。 一般表现为一个类从另一个类公有派生而来。

  3、必需先使用基类指针指向子类型的对象, ?)非类的成员函数不能定义为虚函数, 类的成员函数中静态成员函数和构造函数也不能定义为虚函数, 将基类的析构函数定义为虚函数后, 只调用基类的析构函数。

  ??)如果声明了某个成员函数为虚函数, 则在该类中不能出现和这个成员函数同名并且前往值、参数个数、参数类型都相同的非虚函数。 也不能出现这种非虚的同名同前往值同参数个数同参数类型函数。

  为什么虚函数必需是类的成员函数:

  虚函数降生的目的就是为了完成多态, 在类外定义虚函数毫无实际用处。

  为什么类的静态成员函数不能为虚函数:

  如果定义为虚函数, 那么它就是静态绑定的, 这与静态成员函数的定义(:在内存中只要一份拷贝;通过类名或对象引用拜访静态成员)本身就是相矛盾的。

  为什么构造函数不能为虚函数:

  由于如果构造函数为虚函数的话, 它将在执行时期被构造, 而执行期则需求对象曾经建立, 构造函数所完成的工作就是为了建立合适的对象, 构造的顺序就是从基类到派生类, 其目的就在于确保对象可以成功地构建。 会出现什么情况呢?后果是在构造函数中, 虚函数机制不起作用了, 当基类的析构函数内部有虚函数时, 又如何工作呢?与构造函数相同, 只要“局部”的版本被调用。 但是, 缘由是不一样的。 构造函数只能调用“局部”版本, 是由于调用时还没有派生类版本的信息。 我们知道, 析构函数的调用顺序与构造函数相反, 是从派生类的析构函数到基类的析构函数。 当某个类的析构函数被调用时, 相应的数据也已被丢失, 如果再调用虚函数的派生类的版本, 就相当于对一些不可靠的数据停止操作, 虚函数机制也是不起作用的。

  C++中的虚函数的作用次要是完成了多态的机制。 关于多态, 简而言之就是用父类型别的指针指向其子类的实例, 然后通过父类的指针调用实际子类的成员函数。 这是一种泛型技术。 RTTI技术, 虚函数技术, 要么是试图做到在编译时决议, 要么试图做到运行时决议。

  关于虚函数的使用方法, 我在这里不做过多的阐述。 在这篇文章中, 我只想从虚函数的完成机制下面为大家一个清晰的剖析。

  当然, 大段大段的代码, 没有图片, 没有详细的说明, 没有比拟, 没有触类旁通。 不利于学习和阅读, 所以这是我想写下这篇文章的缘由。 也希望大家多给我提意见。

  言归正传,

  虚函数表

  对C++理解的人都应该知道虚函数(VirtualFunction)是通过一张虚函数表(VirtualTable)来完成的。 在这个表中, 这张表处理了承继、掩盖的问题, 保证其容真实反响实际的函数。 )中这个表被分配在了这个实例的内存中(注:一个类的虚函数表是静态的, 也就是说对这个类的每个实例, 他的虚函数表的是固定的, 不会为每个实例生成一个相应的虚函数表。 ), 它就像一个地图一样, 指明了实际所应该调用的函数。 在C++的标准规格说明书中说到, 编译器必需求保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表, 然后就可以遍历其中函数指针, 并调用相应的函数。 我们可以通过Base的实例来得到Base的虚函数表。 我们可以看到, 我们可以通过强行把&b转成int, 取得虚函数表的地址, 然后, 通过这个示例, 如下所示:

  留意:在下面这个图中, 这个结束标志的值在不同的编译器下是不同的。 我将辨别说明“无掩盖”和“有掩盖”时的子类虚函数表的样子。 我之所以要讲述没有掩盖的情况, 次要目的是为了给一个对比。 再让我们来看看承继时的虚函数表是什么样的。 假设有如下所示的一个承继关系:

  请留意, 在派生类的实例的虚函数表如下所示:

  关于实例:Derived;的虚函数表如下:(overload(重载)和override(重写), 重载就是所谓的名同而签名不同, 重写就是对子类对虚函数的重新完成。 )

  我们可以看到下面几点:

  1)虚函数按照其声明顺序放于表中。

  2)父类的虚函数在子类的虚函数前面。

  一般承继(有虚函数掩盖)

  掩盖父类的虚函数是很显然的事情, 不然, 下面, 如果子类中有虚函数重载了父类的虚函数, 会是一个什么样子?假设, 我们有下面这样的一个承继关系。

  为了让大家看到被承继当时的效果, 我只掩盖了父类的一个函数:f()。 那么,

  1)掩盖的f()函数被放到了子类虚函数表中原来父类虚函数的位置。

  由b所指的内存中的虚函数表(子类的虚函数表)的f()的位置曾经被Derive::f()函数地址所取代, 于是在实际调用发生时, 是Derive::f()被调用了。 这就完成了多态。

  多重承继(无虚函数掩盖)

  下面, 留意:子类并没有掩盖父类的函数。 (所谓的第C++一个父类是按照声明顺序来判别的)

  这样做就是为了处理不同的父类类型的指针指向同一个子类实例, 而可以调用到实际的函数。 如果发生虚函数掩盖的情况。

  我们可以看见, 这样, 如:

  平安性

  每次写C++的文章, 这篇文章也不例外。 通过下面的讲述,

  一、尝试:通过父类型的指针(指向子类对象)拜访子类自己的虚函数

  我们知道, 子类没有重载父类的虚函数是一件毫有意义的事情。 由于多态也是要基于函数重载的。 虽然在下面的图中我们可以看到子类的虚表中有Derive自己的虚函数,

  但在运行时, 我们可以通过指针的方式拜访虚函数表来达到违背C++语义的行为。

  二、尝试:通过父类型的指针(指向子类对象)拜访父类的non-public虚函数

  另外, 如果父类的虚函数是private或是protected的, 但这些非public的虚函数同样会存在于子类虚函数表中, 所以我们同样可以使用拜访虚函数表的方式来拜访这些non-public的虚函数, 这是很容易做到的。

  如:

  结束语

  C++这门言语是一门Magic的言语, 我们似乎永远摸不清楚这门言语背着我们在干了什么。 需求熟悉这门言语, 我们就必需求理解C++里面的那些东西, 需求去理解C++中那些危险的东西。

原文链接: https://www.cnblogs.com/qingqing3721/archive/2011/03/29/1998417.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C++虚函数及虚函数表解析

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/23011

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 上午12:59
下一篇 2023年2月8日 上午1:00

相关推荐