当前位置: 首页 > article >正文

第4章 Function 语意学3: 函数效能、Member Functions、inline

一、函数的效能

二、指向member function的指针

non-static指针

取一个 nonstatic member function 的地址,如果该函数是 nonvirtual,则得到的结果是它在内存中真正的地址。然而这个值需要被绑定于某个 class object 的地址上,才能够通过它调用该函数。所有的 nonstatic memberfunctions都需要对象的地址 (以参数 this 指出).

回顾一下,一个指向 member function 的指针,其声明语法如下

double ( Point::*pmf )();

然后我们可以这样定义并初始化该指针:

double (Point::*coord)() = &Point::x;
或
coord = &Point::y;

想调用它,可以这么做:

( origin.*coord )();
或
( ptr->*coord )();

这些操作会被编译器转化为:

//虚拟 C++码
( coord )( & origin );
//虚拟 C++ 码
( coord )( ptr );

指向 member function 的指针的声明语法,以及指向“member selection 运算符”的指针,是为了可以保留 this 指针的空间。

支持“指向 VirtualMember Functions”之指针

在前一小节中,我们看到了,对一个 nonstatic member function 取其地址,将获得该函数在内存中的地址。

然而,对一个 virtual member function 取其地址,所能获得的仅是 virtual function 在其相关之 virtual table中的索引值,因为其地址在编译时期是未知的,需要动态地去评估。

例如,假设我们有以下的 Point 声明

class Point{
public:
    virtual ~Point();
    float x();
    float y();
    virtual float z();//...
}
float (Point;:*pmf)() = &Point::z;
Point *ptr = new Point3d;

然后取 destructor 的地址,得到的结果是 1。取 x()或 y()的地址,得到的则是函数在内存中的地址,因为它们不是 virtual。取 z()的地址得到的结果是 2。

通过 pmf 来调用 z(),会被内部转化为如下:

( * ptr->vptr[ (int)pmf ])( ptr );

对一个“指向 member function 的指针”会有两种意义:

  • 函数实际地址

  • 虚函数在虚表中的下标

那么如何判断当前的函数指针是哪种含义呢?一种朴素的方式可以进行如下判断,如果当前值大于128,说明是地址,否则说明是下标:

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

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

为了让指向 member functions 的指针也能够支持多重继承和虚拟继承,Stroustrup设计了下面一个结构体:

//一般结构,用以支持
//在多重继承之下指向 member functions 的指针
struct _mptr {
    int delta;
    int index;
    union {
        ptrtofunc faddr;
        int v_offset;
    };
};

index 代表 virtual table 中的索引(当 index 不指向 virtual table 时、会被设为-1),faddr 代表 nonvirtual member function 地址。在该模型之下,调用操作会发生如下转变:

( ptr->*pmf )();

(pmf.index < 0 )
? ( *pmf.faddr )( ptr ) // non-virtual invocation
: ( * ptr->vptr[ pmf.index] (ptr) ); // virtual invocation

delta 字段表示 this 指针的 offset 值,而 v_offset 字段放的是一个 virtual(或多重继承中的第二或后继的)base class 的 vptr位置。此delta字段用于调整 this 指针,指向放置于 virtual table 中的适当地址。所有像这样的调用形式转换:

ptr->virt_func();

// 调用 virtual function
(*ptr->__vptr[ index ].addr)
(ptr + ptr->__vptr[ index ].delta ) // 将 this 指针的调整值传递过去

在这种实现技术之下不论是单一继承或多重继承,只要是虚拟调用操作,就会消耗相同的成本。

三、inline functions

inline 函数的执行成本更低,因为它通过函数拓展,消除了一般的函数调用及返回机制所带来的开销。

一般而言,处理一个 inline 函数,有两个阶段:

1.编译器分析函数定义,以决定函数是否可以支持inline;

2.调用上进行拓展,需要进行以下两步。

  • 参数的求值操作 (evaluation):每一个形式参数都会被对应的实际参数取代

  • 临时性对象的管理

举个例子,假设我们有以下的简单 inline 函数:

inline int
min( int i,int j )
{ return i < j ? i : j; }

下面是三个调用操作

inline int
bar(){
    int minval;
    int val1 = 1024;
    int val2 = 2048;
    /* (1) */minval = min( vall, val2 );
    /* (2) */minval = min( 1024,2048 );
    /* (3) */minval = min( foo(), bar()+1 );
    return minval;
}

标示为 (1)的那一行会被扩展为:

// (1)参数直接代换
minval = vall < val2 ? val1 : val2;

标示为(2)的那一行直接拥抱常量

// (2) 代换之后,直接使用常量
minval = 1024;

标示为(3)的那一行则引发参数的副作用。它需要导人一个临时对象,以避免重复求值 (multiple evaluations):

// (3)有副作用,所以导人临时对象
int tl;
int t2;
minval =
( t1 = foo() ),( t2 = bar() + 1 ),
tl < t2 ? t1 : t2;

局部变量

如果在 inline 定义中加一个局部变量,并进行以下调用

inline int
min( int i, int j ){
int minval = i < j ? i :j; return minval;
}

int local var;
int minval;
//...
minval = min( val1, val2 )

inline 被扩展开来后,为了维护其局部变量,可能会成为这个样子 (理论上这个例子中的局部变量可以被优化,其值可以直接在 minval 中计算):

int local_var;
int minval;
//将 inline 函数的局部变量处以“mangling”操作
int  min_lv_minval;
minval = 
(min_lv_minval = vall < val2 ? vall : val2 ),
min_lv_minval);


http://www.kler.cn/a/581303.html

相关文章:

  • 调试正常 ≠ 运行正常:Keil5中MicroLIB的“量子态BUG”破解实录
  • 几种常见的去除白色背景的方式详解
  • golang recover错误
  • OpenGL ES ->帧缓冲对象(Frame Buffer Object)离屏渲染获取纹理贴图
  • 【6】字典树学习笔记
  • ClusterIP、Headless Service 和 NodePort 的比较
  • 如何提取神经网络中间层特征向量
  • 责任链模式的C++实现示例
  • 软考高级信息系统项目管理师笔记-第19章配置与变更管理
  • 接口自动化入门 —— swagger/word/excelpdf等不同种类的接口文档理解!
  • Ollama杂记
  • 【CXX】6.4 CxxString — std::string
  • LeetCode100之二叉树的直径(543)--Java
  • 牵引线标注:让地图信息更清晰的ArcGIS Pro技巧
  • 制作自定义镜像
  • docker-compose Install m3e(fastgpt扩展) GPU模式
  • 跨公网 NAT 配置方案:实现高效网络通信与安全访问
  • 关于在vue3中使用keep-live+component标签组合,实现对指定某些组件进行缓存或不缓存的问题
  • 【软考-架构】2.3、设备管理-文件管理
  • flinkOracleCdc任务报错kafkaConnectSchema