《深度探索c++对象模型》第四章笔记

非原创,在学习

4 Function语意学( The Semantics of Function )

假如有一个Point3d的指针和对象:

cpp 复制代码
Point3d obj;
Point3d* ptr = &obj;

这样做

cpp 复制代码
obj.normalize();
ptr->normalize();

时,会发生什么事?其中的Point3d::normalize()定义如下:

cpp 复制代码
Point3d
Point3d::normalize() const
{
    // magnitude是一个函数,magnitude (Read Only)返回向量的长度,
    // 也就是点P(x,y,z)到原点(0,0,0)的距离
    register float mag = magnitude();
    Point3d normal;

    normal._x = _x / mag;
    normal._y = _y / mag;
    normal._z = _z / mag;
    return normal;
}

其中Point3d::magnitude ()又定义如下:

cpp 复制代码
float Point3d::magnitude() const
{
    return sqrt(_x * _x + _y * _y + _z * _z);
}

答案是:不知道!C++支持三种类型的 member functions**: static、nonstatic和virtual**,每一种类型被调用的方式都不相同。其间差异正是下一节的主题。不过,我们虽然不能够确定normalize()和magnitude()两函数是否为virtual或nonvirtual,但可以确定它一定不是 static,原因有二:(1)它直接存取nonstatic数据;(2)它被声明为const。是的,static mermber functions不可能做到这两点。

4.1 Member 的各种调用方式

virtual函数是20世纪80年代中期被加进来的;

Static member functions是最后被引人的一种函数类型。它们在1987年的Usenix C++研讨会的厂商研习营(Implementor's Workshop)中被正式提议加人C++中,并由 cfront 2.0实现出来.

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

C++的设计准则之一就是:nonstatic member function 至少必须和一般的nonmember function 有相同的效率。也就是说,如果我们要在以下两个函数之间作选择:

cpp 复制代码
float magnitude3d(const Point3d* _this) {  }
float Point3d::magnitude3d() const {  }

那么选择member function不应该带来什么额外负担。这是因为编译器内部已将"member函数实体"转换为对等的"nonmember函数实体"。

举例:

cpp 复制代码
float magnitude3d(const Point3d* _this) {
    return sqrt(_this->_x * _this->_x +
                _this->_y * _this->_y +
                _this->_z * _this->_z);
}

乍见之下似乎nonmember function比较没有效率,它间接地经由参数取用坐标成员,而member function 却是直接取用坐标成员.**然而实际上 member function被内化为nonmember的形式。**下面就是转化步骤:

1 改写函数的signature以安插一个额外的参数到member function中,用以提供一个存取管道,是class object得以调用该函数,该额外参数被称为this指针:

cpp 复制代码
// non-const nonstatic member之增长过程
Point3d
Point3d::magnitude(Point3d* const this)

如果member function是const,则变成:

cpp 复制代码
// const nonstatic member之扩张过程
Point3d
Point3d::magnitude(const Point3d* const this)

就是加形参:

(1)如果是普通(非静态、非const)成员函数,加Point3d* const this这个参数

(2)如果是const成员函数,加const Point3d* const this这个参数

2 将每一个"对nonstatic data member的存取操作"改为经由this指针来存取:

cpp 复制代码
{
    return sqrt(_this->_x * _this->_x +
                _this->_y * _this->_y +
                _this->_z * _this->_z);
}

3.将member function重新写成一个外部函数.对函数名称进行"mangling"处理,使它在程序中成为独一无二的语汇:

Name Mangling 是一种在编译过程中,将函数、变量的名称重新改编的机制

cpp 复制代码
extern magnitude__7Point3dFv(
    register Point3d *const this);

现在这个函数已经被转换好了,而其每一个调用操作也都必须转换。于是:

cpp 复制代码
obj.magnitude();

变成了

cpp 复制代码
magnitude__7Point3dFv(&obj);

cpp 复制代码
ptr->magnitude();

变成了

cpp 复制代码
magnitude__7Point3dFv(ptr);

本章一开始所提及的normalize()函数会被转化为下面的形式,其中假设已经声明有一个Point3d copy constructor,而named returned value (NRV)的优化也已施行:

cpp 复制代码
// C++伪码
void
normalize__7Point3dFv(register const Point3d* const this,
                        Point3d& _result) 
{
    // magnitude是一个函数,magnitude (Read Only)返回向量的长度,
    // 也就是点P(x,y,z)到原点(0,0,0)的距离
    register float mag = this->magnitude();
    _result.Point3d Point3d();

    _result._x = this->_x / mag;
    _result._y = this->_y / mag;
    _result._z = this->_z / mag;
    return ;
}

一个比较有效率的做法是直接建构"normal"值,像这样:

cpp 复制代码
Point3d
Point3d::normalize() const
{
    register float mag = magnitude();
    return Point3d(_x / mag, _y / mag, _z / mag);
}

它会被转化为以下的码(我再一次假设Point3d的copy constructor已经声明好了,而NRV的优化也已实施):

cpp 复制代码
// C++伪码
void
normalize__7Point3dFv (register const Point3d* const this,
                        Point3d& _result)
{
    register float mag = this->magnitude();
    // _result用以取代返回值
    _result.Point3d::Point3d(this->_x / mag, this->_y / mag, this->_z / mag);
    return;
}

这可以节省default constructor初始化所引起的额外负担。

名称的特殊处理( Name Mangling )

由于member functions可以被重载化 ( overloaded),所以需要更广泛的mangling手法,以提供绝对独一无二的名称。

为了让它们独一无二,唯有再加上它们的参数链表(可以从函数原型中参考得到)。如果把参数类型也编码进去,就一定可以制造出独一无二的结果,使我们的两个x()函数有良好的转换。

Virtual Member Functions(虚拟成员函数)

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

cpp 复制代码
ptr->normalize();

将会被内部转化为:

cpp 复制代码
(*ptr->vptr[1])(ptr);

其中:

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

类似的道理,如果magnitude()也是一个virtual function,它在normalize()之中的调用操作将被转换如下:

cpp 复制代码
// register float mag = magnitude();
register float mag = (*this->vptr[2])(this);

此时,由于Point3d::magnitude()是在 Point3d:.normalize()中被调用,而后者已经由虚拟机制而决议((resolved)妥当,所以明确地调用"Point3d实体"会比较有效率,并因此压制由于虚拟机制而产生的不必要的重复调用操作:

cpp 复制代码
// 明确的调用操作会压制虚拟机机制
register float mag = Point3d::magnitude();

如果magnitude(声明为inline函数会更有效率。使用class scope operator明确调用一个virtual function,其决议(resolved)方式会和nonstatic member function一样:

cpp 复制代码
register float mag = magnitude__7Point3dFv(this);

对于以下调用:

cpp 复制代码
// Point3d obj;
obj.normalize();

如果编译器把它转换为:

cpp 复制代码
(*obj.vptr[1])(&obj);

虽然语意正确,却没有必要。请回忆那些并不支持多态( polymorphism)的对象( 1.3节)。所以上述经由obj 调用的函数实体只可以是 Point3d::normalize() ,"经由一个 class object调用一-个virtual function",这种操作应该总是被编译器像对待一般的nonstatic member function 一样地加以决议(resolved) :

cpp 复制代码
normalize__7Point3dFv(&obj);

这项优化工程的另一利益是,virtual function的一个inline函数实体可以被扩展( expanded)开来,因而提供极大的效率利益。

Static Member Functions(静态成员函数)

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

cpp 复制代码
obj.normalize();
ptr->normalize();

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

cpp 复制代码
// obj.normalize();
normalize()__7Point3dSFv();

// ptr->normalize();
normalize()__7Point3dSFv();
相关推荐
knighthood20012 分钟前
解决:ros进行gazebo仿真,rviz没有显示传感器数据
c++·ubuntu·ros
Source.Liu13 分钟前
【用Rust写CAD】第二章 第四节 函数
开发语言·rust
monkey_meng14 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马17 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng20 分钟前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
Jacob程序员22 分钟前
java导出word文件(手绘)
java·开发语言·word
小白学大数据29 分钟前
正则表达式在Kotlin中的应用:提取图片链接
开发语言·python·selenium·正则表达式·kotlin
VBA633731 分钟前
VBA之Word应用第三章第三节:打开文档,并将文档分配给变量
开发语言
半盏茶香31 分钟前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
小堇不是码农37 分钟前
在VScode中配置C_C++环境
c语言·c++·vscode