《深度探索C++对象模型》阅读笔记 第四章 Function语意学


C++支持三种类型的member functions:static、nonstatic和virtual。

Member 的各种调用方式

Nonstatic Member Functions(非静态成员函数)
C++的设计准则之一就是:nonstatic member function至少必须和一般的nonmember function有相同的效率。
名称的特殊处理(Name Mangling)一般而言,member的名称前面会被加上class名称,形成独一无二的命名。
Virtual Member Functions(虚拟成员函数)

( * ptr->vptr[1])( ptr )
  • vptr表示由编译器产生的指针,指向virtual table。它被安插在每一个“声明有(或继承自)一个或多个 virtual functions”的class object中。事实上其名称也会被“mangled”,因为在一个复杂的class派生体系中,可能存在多个vptrs。
  • 1是virtual table slot的索引值,关联到 normalize()函数。
  • 第二个ptr表示this指针。

Static Member Functions(静态成员函数)

Virtual Member Functions(虚拟成员函数)

virtual function的一般实现模型:每一个class有一个virtual table,内含该class之中有作用的virtual function的地址,然后每个object有一个vptr,指向virtual table的所在。
在C++中,多态(polymorphism)表示“以一个public base class 的指针(或reference),寻址出一个derived class object”的意思。
C++对“积极多态(active polymorphism)”的唯一支持,就是对于virtual function call的决议(resolution)操作。有了RTTI,就能够在执行期查询一个多态的pointer或多态的reference了。
欲鉴定哪些 classes 展现多态特性,我们需要额外的执行期信息。一如我所说,关键词class和struct并不能够帮助我们。由于没有导入像是polymorphic之类的新关键词,因此识别一个class是否支持多态,唯一适当的方法就是看看它是否有任何virtual function。只要class拥有一个virtual function,它就需要这份额外的执行期信息。
在实现上,首先我可以在每一个多态的class object身上增加两个members:

  1. 一个字符串或数字,表示class的类型;
  2. 一个指针,指向某表格,表格中持有程序的virtual functions的执行期地址。

然而,执行期备妥那些函数地址,只是解答的一半而已。另一半解答是找到那些地址。两个步骤可以完成这项任务:

  1. 为了找到表格,每一个class object被安插了一个由编译器内部产生的指针,指向该表格。
  2. 为了找到函数地址,每一个virtual function被指派一个表格索引值。

这些工作都由编译器完成。执行期要做的,只是在特定的virtual table slot(记录着virtual function的地址)中激活virtual function。
一个class只会有一个virtual table。每一个table内含其对应之class object中所有active virtual functions函数实例的地址。这些active virtual functions包括:

  • 这一class所定义的函数实例。它会改写(overriding)一个可能存在的base class virtual function函数实例。
  • 继承自base class的函数实例。这是在derived class决定不改写virtual function时才会出现的情况。
  • 一个pure_virtual_called()函数实例,它既可以扮演pure virtual function的空间保卫者角色,也可以当做执行期异常处理函数(有时候会用到)。每一个virtual function都被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的virtual function的关系。
    <a name=image.png&originHeight=667&originWidth=738&originalType=binary&ratio=1&rotation=0&showTitle=true&size=278302&status=done&style=none&taskId=ue1ca23ed-be0d-4a7b-a88b-42He4l06036&title=Virtual Tahle的布局:多重继承情况&width=536.7272727272727 “Virtual Table的布局:多重继承情况”)

单一继承下的Virtual Functions

在这里插入图片描述


现在,如果我有这样的式子:ptr->z()
我如何有足够的知识在编译时期设定virtual function的调用呢?

  • 一般而言,在每次调用 z()时,我并不知道ptr所指对象的真正类型。然而我知道,经由 ptr可以存取到该对象的virtual table。
  • 虽然我不知道哪一个z()函数实例会被调用,但我知道每一个z()函数地址都被放在slot 4中。这些信息使得编译器可以将该调用转化为:(*ptr->vptr[4])(ptr)

多重继承下的Virtual Functions

在多重继承中支持virtual functions,其复杂度围绕在第二个及后继的base classes身上,以及“必须在执行期调整this指针”这一点。

在这里插入图片描述

虚拟继承下的Virtual Functions

在这里插入图片描述

虚拟继承中,虚基在派生类中也存在虚指针,存在多个虚函数表,更复杂。

函数的效能

nonmember、static member或nonstatic member函数都被转化为完全相同的形式。所以我们毫不惊讶地看到三者的效率完全相同

指向Member Function的指针

使用一个“member function指针”,如果并不用于virtual function、多重继承、virtual base class等情况的话,并不会比使用一个“nonmember function指针”的成本更高。
对一个nonstatic member function取其地址,将获得该函数在内存中的地址。然而面对一个virtual function,其地址在编译时期是未知的,所能知道的仅是virtual function在其相关之virtual table中的索引值。也就是说,对一个virtual member function取其地址,所能获得的只是一个索引值。

Inline Functions

关键词inline只是一项请求。如果这项请求被接受,编译器就必须认为它可以用一个表达式(expression)合理地将这个函数扩展开来。
一般而言,处理一个inline函数,有两个阶段:

  1. 分析函数定义,以决定函数的“intrinsic inline ability”(本质的 inline能力)。“intrinsic”(本质的、固有的)一词在这里意指“与编译器相关”。如果函数因其复杂度,或因其建构问题,被判断不可成为inline,它会被转为一个static函数,并在“被编译模块”内产生对应的函数定义。
  2. 真正的inline函数扩展操作是在调用的那一点上。这会带来参数的求值操作(evaluation)以及临时性对象的管理。

形式参数(Formal Arguments)
每一个形式参数都会被对应的实际参数取代。如果说有什么副作用,那就是不可以只是简单地一一封塞程序中出现的每一个形式参数,因为这将导致对于实际参数的多次求值操作(evaluations),所以可能会引入临时变量。

在这里插入图片描述

局部变量(Local Variables)
一般而言,inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段中,拥有一个独一无二的名称。如果inline函数以单一表达式(expression)扩展多次,则每次扩展都需要自己的一组局部变量。如果inline函数以分离的多个式子(discrete statements)被扩展多次,那么只需一组局部变量,就可以重复使用(译注:因为它们被放在一个封闭区段中,有自己的scope)。
然而一个inline函数如果被调用太多次的话,会产生大量的扩展码,使程序大小暴涨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值