《深度探索C++对象模型》读书笔记[第四章:Function语义学]

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

4.1 Member 的各种调用方式

4.1.1 Nonstatic Member Functions(非静态成员函数)

非静态成员函数会被内化为nonmember 的形式.步骤:

  1. 改写函数原型,以安插this指针.
Point3d::magnitude()
// 转化为
Point3d::magnitude(Point3d* const this)
// 如果member function是const,则变成:
Point3d::magnitude(const Point3d* const this)

2.将每一个"对 nonstatic data member 的存取操作"改为经由 this 指针来存取.
3.将member function 重新写成一个外部函数.对函数名称进行"mangling"处理,使它在程序中称为独一无二的词汇.

4.1.2 名称的特殊处理(Name Mangling)

一般而言,member的名称前面那会被加上 class 名称,形成独一无二的命名.这样可以区分基类与子类中重名的变量名.

由于 member cuntions 可以被重载化(overloaded),所以需要更广泛的 mangling 手法,以提供绝对独一无二的名称.即:函数名称除了加上类名称外,还需要加上参数列表的名称.(如果声明 extern "C",就会压抑 nonmember functions的 "mangling"效果)

4.1.3 Virtual Member Functions(虚拟成员函数)

如果 normalize()是一个 virtual member function,那么以下的调用:

ptr->normalize();

将会被内部转化为

(&ptr->vptr[1])(ptr);

其中:

  • vptr是指向virtual table 的指针,其名称也会被"mangled",因为在一个复杂的class 派生体系中,可能存在有多个 vptrs.
  • 1是 virtual table slot 的索引值,关联到 normalize()函数.
  • 第二个 ptr 表示 this 指针

4.1.4 Static Member Functions(静态成员函数)

如果 Point3d::normalize()是一个 static member function,以下两个调用操作:

obj.normailze();
ptr->normalize();

将被转换为一般的 nonmember 函数调用,像这样:

// obj.normailze();
normalize__7Point3dSFv();
// ptr->normalize();
normalize__7Point3dSFv();

Static member functions的主要特性就是它没有this指针.以下次要特性统统根源于其主要特性:

  • 它不能够直接存取其 class 中的 nonstatic members.
  • 它不能否被声明为 const、volatile 或 virtual.
  • 它不需要经由 class object 才被调用----虽然大部分时候它是这样被调用的.

如果取一个 static member function 的地址,获得的将是其在内存中的位置,也就是其地址.由于 static member function 没有this指针,所以其地址的类型并不是一个"指向 class member function 的指针",而是一个 "nonmember 函数指针"。也就是说:
&Point3d::object_count();
会得到一个数值,类型是:
unsigned int (*)();
而不是:
unsigned int (Point3d::*)();

Static member function 由于缺乏 this 指针,因此差不多等同于 nonmember function.它提供了一个意想不到的好处:称为一个 callback 函数.

4.2 Virtual Member Functions(虚拟成员函数)

为了支持 virtual function 机制,必须授信能够对于多态对象有某种形式的"执行器类型判断法(runtime type resolution)"。也就是说,以下的调用操作将需要 ptr 在执行器的某些相关信息,
ptr-z();
如此能够找到并调用z()的适当实体.

在C++中,多态表示"以一个 public base class 的指针(或reference),寻址出一个 derived class object"的意思。

欲鉴定哪些 classes 展现多态特性,我们需要额外的执行器信息.识别一个 class 是否支持多态,唯一适当的方法就是看看它是否有任何 virtual function。只要 class 拥有一个 virtual function,它就需要这份额外的执行期信息.

第二个问题是 什么样的额外信息使我们需要存储起来的?
实现上,首先我们可以在每一个多态的 class object 身上增加两个 members:

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

表格中的 virtual functions 地址如何被构建起来?在C++中, virtual functions(可经由其 class object 被调用) 可以在编译时期获知。表格的大小和内容在执行期都不会改变,由编译器完全掌握,不需要执行期的任何介入.
编译器备妥函数地址只是解答了一半,另一半是找到那些地址.
1.为了找到表格,每个 class object 被安插上一个由编译器内部产生的指针,指向该表格。
2.为了找到函数地址,每一个 virtual function 被指派一个表格索引值.
这些工作仍然由编译器完成.执行期要做的,只是在特定的 virtual table slot(记录着 virtual function 的地址)中激活 virtual function。

一个 class 只会有一个 virtual table(但一个 object 可能有多个 vptr!!!)。每一个 slot 内含有对应的 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 的关联。
7bec6aebf9ed79d415c0565ac947e7b4.png

当一个 class 派生自 Point 时,会发生:

  1. 它可以继承 base class 所声明的 virtual functions 的函数实体.正确地说,是该函数实体的地址会被拷贝到 derived class 的 virtual table 相对地址之中.
  2. 它可以使用自己的函数实体.这表示它自己的函数实体地址必须放在对用的slot之中.
  3. 它可以加入一个新的 virtual function.这时候 virtual table 的尺寸会增大一个slot,而心得函数实体会被放进该 slot 之中.

now,有这样的式子:

ptr->z();

如何有足够的只是在编译时期设定 virtual function 的调用呢?

  • 一般而言,并不知道ptr 所指对象的真正类型.然而可以知道又 ptr 可以村渠道该对象的 virtual table.
  • 虽然不知道哪一个 z() 函数实体会被调用,但可以知道每一个 z()函数地址都被放在 slot4.
    这些信息使得编译器可以将该调用转化为: (*ptr->vptr[4])(ptr);
    在这个转化中,vptr 表示编译器所安插的指针,指向 virtual table;4 表示 z()被赋值的slot编号(关联到 Point 体系的virtual table).唯一一个在执行期才能知道的的东西是: slot 4 所指的到底是哪一个z() 函数实体?

4.2.2 多重继承下的 Virtual Functions

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

graph TD A[Base1] -->|Public| C(Derived) B[Base2] -->|Public| C(Derived)

"Derived 支持 virtual functions"的困难度,在于 Base2 subobject身上,需要解决三个问题:(1) virtual destructor,(2)被继承下来的 Base2::mumble();(3)一组 clone() 函数实体。

在多重继承之下,一个 derived class 内含 n-1 个额外的 virtual tables,n 表示其上一层 base classes 的数目(因此,单一继承将不会有额外的 virtual tables).vptrs 将在 constructor(s)中被设立初值(经由编译器所产生出来的码).

用以支持“一个 class 拥有多个 virtual tables”的传统方法是,将每一个 tables 以外部对象的形式产生出来,并赋予独一无二的名称.

4.2.3 虚拟继承下的 Virtual Functions

只有一句忠告:不要在一个 virtual base class 中声明 nonstatic data members.如果这么做,你会距离复杂的深渊越来越近,终不可拔.

4.2.4 指向 Member Functions 的指针

4.2.1 支持 "指向 Virtual Member Functions"之指针

对一个 nonstatic member function 取地址,将得到该函数在内存中的地址.而对一个 virtual function取地址,所能获得的只是一个索引值.对于一个"指向 member function 的指针"[float (Point::*pmf)()]必须允许该函数能够寻址出 nonvirtual x()和 virtual z()两个 member functions。识别该值是内存地址还是 virtual table索引的技巧:

((( int ) pmf ) & ~127 )
    ?       // non-virtual invocation
    ( *pmf ) ( ptr )
    :       // virtual invocation
    ( * ptr->vptr[(int) pmf] (ptr) );

4.2.2 在多重继承之下,指向 Member Functions 的指针

许多编译器在自身内部根据不同的 classes 特性提供多种指向 member functions 的指针形式.

4.5 Inline Functions

inline只是一份建议

posted @ 2022-02-17 12:17  liyakai  阅读(57)  评论(0编辑  收藏  举报