《深入探索C++对象模型》笔记 Chapter4 成员函数

《深入探索C++对象模型》笔记 Chapter2 构造函数
《深入探索C++对象模型》笔记 Chapter3 成员变量
《深入探索C++对象模型》笔记 Chapter4 成员函数

第4章 成员函数

4.1 总述

非静态成员函数

C++的设计准则之一就是,非静态成员函数至少必须要和普通函数有相同的效率。

编译器会将成员函数转换为了对等的普通函数。转换步骤如下:

  • 改写函数原型(signature),传入参数添加一个this指针
  • 将对成员变量(非静态)的操作改为通过this指针来存取
  • 对成员函数的函数名称做命名重整(mangling)

上述所说的 mangling 过程,通常函数名会被加上class名称的前缀,以及参数类型的后缀,从而确保每个函数名都是独一无二的。

虚函数

//虚函数调用
ptr->func();
//转为为
(*ptr->vptr[1])(ptr);

以上的转换,vptr表示指向虚函数表的指针;1是虚函数表中该函数的索引值,和func()函数关联;函数参数中的ptr表示this指针。

静态成员函数

首先来看看为什么需要静态成员函数。

在引入静态成员函数之前,C++规定所有成员函数都必须由对象来调用,然而有些成员函数不对成员变量操作,也就没必要传入this指针,而C++并不能辨识这种情况。

于是,如果将静态成员变量声明为private,就必须提供成员函数来调用它,也就必须要实例化一个对象才能调用,以至出现了以下奇葩的写法:
((ClassName*) 0 ) -> get_staticmember_func();
将0强转成class指针,然后调用成员函数返回一个静态成员变量。

而有了静态成员函数,才解决了上述所说的问题。静态成员函数的主要特性是没有this指针,以此延伸出了其他特性:

  • 不能存取非静态成员
  • 不能被声明为 const volatile virtual
  • 不需要经由对象才能被调用

由于静态成员函数没有this指针,所以它在内存上的存储类似于普通函数。

4.2 虚函数

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

单继承

单继承内存模型

如上图, Point3d 继承 Point2d , Point2d 继承 Point 。

virtual table 在编译期就已经确定,virtual table 的每一个函数地址称之为一个 slot ,派生类定义虚函数会 overriding 基类相应函数的 slot 。这样 ptr->z() ,不管 ptr 指向的对象是基类还是派生类,它调用 z() 时函数地址一定是放在第四个 slot ,于是编译器转换代码为 (*ptr->vptr[4])(ptr)

对于纯虚函数,slot 里放置的是 pure_virtual_called() 函数,这个函数如果被意外调用,通常会结束掉这个程序。可以利用这个特性做运行期异常处理。

多继承

多继承内存模型

虚继承

虚继承内存模型

4.3 效率

经过测试,普通函数、非静态成员函数、静态成员函数的效率很相近。inline成员函数效率惊人。

4.4 指向成员函数的指针

A::* 意为指向类A成员的指针,我们可以用 void (A::*pmf)() = &A::func 表示指向A中 func() 成员函数的指针。对于非虚函数,这个指针指向一个函数地址,对于虚函数,由于地址在编译时期是未知的,所以存放的是该函数在虚函数表中的索引值。(个人以为,此时再说 pmf 是个指针就比较牵强了,它只是个记录函数相对位置的int值)
于是,(ptr->*pmf)() 就会被编译器翻译为 (*ptr->vptr[(int)pmf])(ptr) 。可以写个demo做个验证:

#include <iostream>
#include <stdio.h>
using namespace std;

class A {
    public:
    void common(){}
    virtual void foo() { printf("A::foo(): this = 0x%p\n", this); }
};
class B :public A{
    public:
    virtual void foo() { printf("B::foo(): this = 0x%p\n", this); }
    virtual void bar() { printf("B::bar(): this = 0x%p\n", this); }
    virtual void foo(int i) { printf("B::bar(): this = 0x%p\n", this); }
};
void (A::*pafoo)() = &A::foo;  
void (B::*pbfoo)() = &B::foo;   
void (B::*pbbar)() = &B::bar;
void (A::*pcommon)() = &A::common;
int main(){
    A* a = new A;
    B* b = new B;
    printf("A::commont: %x\n",pcommon);
    printf("A::foo: %x\n",pafoo);
    printf("B::foo: %x\n",pbfoo);
    printf("B::bar: %x\n",pbbar);
}

输出如下:

A::commont: 31425c9e
A::foo: 1
B::foo: 1
B::bar: 9

至于为什么索引值从1开始,书上没有说,不过想来和 3.6指向成员函数的指针 所说的类似,为了区分指向成员函数的空指针。

而索引值按8递增,那是因为我的云主机是64位,一个指针8字节。

单继承的情况,一个索引值就足够调用不同虚函数了,但是考虑到多继承呢?一个派生类继承自A和B,调用它的虚函数时,this指针作为传入参数可能是A,也可能是B,所以多继承还需要考虑this指针的偏移。我本来想仿照此篇博客 C/C++杂记:深入理解数据成员指针、函数成员指针 打印出this指针的偏移,但是事与愿违,偏移值始终为0,也只能归因于不同编译器的实现方式不同了。

4.5 内联函数

inline关键字并不能强迫将任何函数都变成 inline ,而是给编译器一个建议。至于编译器是否采纳这个建议,需要一系列复杂的测试,包括计算赋值、调用函数、调用虚函数的次数,每种操作都会有一个权值,这些操作与权值的乘积之和就是函数的复杂度。

对于inline函数,如果传入的参数是变量或者常量,那么直接对函数体内代码执行参数替换就可以了,但是如果传入的参数是个表达式呢?难道函数体内的代码每次碰到这个表达式都要算一遍?显示编译器不会这么傻。针对这种情况,编译器会产生一个临时对象,以避免重复求值。

再考虑一种情况,如果inline函数中有局部变量会怎么样?如果直接把代码扩展到调用inline函数的函数,万一后者有同名的变量呢?所以需要把局部变量放在封闭区段中,让它们有自己的作用域。这个过程依旧会产生局部变量。

可以看到,局部变量和表达式参数都会使inline函数产生临时对象,所以使用inline不当会产生大量临时对象反而降低效率。

参考文章

C/C++杂记:深入理解数据成员指针、函数成员指针

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容

  • 一个博客,这个博客记录了他读这本书的笔记,总结得不错。《深度探索C++对象模型》笔记汇总 1. C++对象模型与内...
    Mr希灵阅读 5,590评论 0 13
  • 1.C和C++的区别?C++的特性?面向对象编程的好处? 答:c++在c的基础上增添类,C是一个结构化语言,它的重...
    杰伦哎呦哎呦阅读 9,528评论 0 45
  • 1. 让自己习惯C++ 条款01:视C++为一个语言联邦 为了更好的理解C++,我们将C++分解为四个主要次语言:...
    Mr希灵阅读 2,812评论 0 13
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,521评论 1 51
  • 上一章 天人族的阿迦斯收到了诺顿让提婆达多带来的最后一份情报,诺顿建议阿迦斯赶快派一支特别远征队来到地球,把人类的...
    marvinmwb阅读 159评论 0 1