c++对象模型解析(一)

    本系列记录在学习过程中,我对于c++对象模型的理解,主要包括如下知识点等底层实现机制。

目录

struct 和 class之间关系区别

多态的实现方法

对象的大小

构造函数

NRV(Named Return Value)优化

inline函数、构造函数与虚函数

数据继承模型


struct 和 class之间关系区别

  1. 成员变量权限,首先struct中的成员变量默认是私有的,class的成员变量默认是共有的
  2. 继承方式,struct默认是私有继承,class默认是公有继承
  3. 在定义模板参数的时候只能用class,不能用struct
  4. 最重要的一点是语义上,关键词本身并不提供差异,当看到一个struct,下意识会想到struct其实是一个“数据集合”的意思,而class则是一个“类”的意思,并包含了封装、继承、多态等属性

多态的实现方法

    多态指的是同一个语句的不同表现形态(同一个接口,不同的实现形态)。

  1. 经由一组隐式转换操作,比如将一个指向derived类对象的指针转换成指向父类的指针
  2. 经由virtual function机制
  3. 经由dynamic_cast 和 typ_id运算符

    这里要理解一个问题,为什么只能通过引用或者指针的形式才能触发多态,而一个普通的赋值不能触发多态机制?如果通过普通的赋值则会发生切割对象的问题,即把部分子类对象的成员变量切割掉,而指针则不会,因为其大小不变,根据指针类型变化的只有指针所指内存大小,这才会产生多态。引用本质实现上也是指针,所以相一致。

对象的大小

    一般而言有三种组成,

  1. non-static 数据成员变量大小总和
  2. 如果类中定义了虚函数,则需要加虚指针的大小
  3. 对其所填补的空间,如32位机器调整到4字节的整数倍,目的是内存与cpu之间总线传输每次能传完整数倍,提高效率

构造函数

  • 什么时候编译器会为类A产生默认构造函数?

    首先编译器为类A产生默认构造函数肯定是因为觉得构造函数有必要(nontrivial)且这个类的创建者没有写构造函数,但是有一点要注意,这个产生的默认构造函数只是满足编译器需求,并不一定满足程序的要求,所以如果要指定对成员变量进行初始化,还是最好自己生成构造函数。

  1. 类A成员中有一个类B的对象,类B是有构造函数的(组合关系)
  2. 类A继承于另外一个类B,B有构造函数
  3. 类A声明了虚函数或者有继承的虚函数
  4. 类继承于虚基类
  • 什么时候编译器会为类产生拷贝构造函数?

    以下四种情况,若无显示定义拷贝构造函数,则编译器会自动生成默认拷贝构造函数(和默认构造函数是一样的),因为编译器必须将成员或者基类的拷贝构造函数在合成的拷贝构造函数中调用。

  1. 当类A内含有一个类B的成员变量,而且其拥有拷贝构造函数
  2. 当类A继承自一个基类B,基类B有拷贝构造函数
  3. 当类A声明一个或多个虚函数
  4. 当类A继承自一个或多个虚基类
  • 什么时候必须使用成员初始化列表?

    成员初始化列表是构造函数用于按顺序初始化成员变量,其顺序最好和变量的声明顺序一致,不一致会导致一些问题(且较难debug,别问为啥知道),除此之外还有一点,编译器保证初始化列表中的初始化操作会比函数主体中的初始化要前,且代价较小,以下四种情况必须使用初始化列表:

  1. 当初始化一个变量引用时
  2. 当初始化一个const变量时
  3. 当调用一个基类的构造函数,且其拥有参数
  4. 当调用一个成员变量的构造函数,且其拥有参数

NRV(Named Return Value)优化

    这个是编译器对于c++中函数中返回局部变量的一种优化方法,即把其通过传入一个引用参数而取出。注意一点,《对象模型》说道“只有类中定义了显示拷贝构造函数,才能触发编译器NRV”,但实际上如今的g++无论你是否定义拷贝构造,都会进行优化。

具体实现步骤:

  1. 函数添加一个额外参数,为返回对象的引用;
  2. 函数调用前,先申请欲返回对象x2的内存空间;
  3. 将对象x2的引用传入函数中,并在函数返回前,调用x2的拷贝构造函数。
class X{...};

X foo(){
    X x1;
    //对x1进行处理
    return x1;
}

X x2=foo();

--------优化后------

void foo(X& x2){

    X x1;
    //对x1进行处理
    x2.X::X(x1);//调用拷贝构造函数
    return;
}

X x2;
foo(x2);

inline函数、构造函数与虚函数

  • inline函数能否定义为虚函数?

首先,inline函数的意思就是减少函数调用的开销,当遇到函数调用的时候,直接将函数主体迁移至调用的地方,而且是在编译期间做的,是静态行为,而虚函数则需要在运行期间才能确定动态类型,两者产生冲突,除此之外,我们定义的inline函数也只是对编译器的一种推荐,具体编译器是否声明其为inline函数有自己的规则,所以最好不要将inline函数定义为虚函数。

  • 构造函数能否定义为虚函数?构造函数中能否调用虚函数?

首先先明确结论,构造函数不能定义为虚函数,析构函数可以定义为虚函数(这个问题不解释了),而且不要在构造函数和析构函数中调用虚函数(Effective C++条款9,主要是基类和派生类的构造顺序有关)。虚表存放在进程空间中的数据段中,由多个对象所共享,所以可以看成静态成员变量,而虚指针则是在类对象的内部,在构造函数(进入构造函数体之前,也就是和初始化列表一样)初始化

  1. 虚函数是需要一个虚表存放其地址,然后每个对象通过虚指针指向这个虚表进而访问虚函数,而对象恰恰是通过构造函数来初始化虚指针和虚表的,所以两者矛盾。
  2. 虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的相应的成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

数据继承模型

  • 当一个对象没有加入多态机制(即对象内部没有声明虚函数),对象的大小只用关心数据大小和alignment即可。这里还需要注意点,当派生类继承基类的时候,基类的大小已经添加过alignment,派生类对象的大小基于此再增加。
  • 单一继承当加上多态机制后,就要付出额外的代价:
  1. 新建一个与类相关的虚表,用来存放虚指针的地址,虚表中的元数个数一般为类中声明的虚函数个数+一个slot(RTTI)
  2. 在每个对象中都有一个虚指针指向虚表,用于运行期使得对象能够找到虚表
  3. 构造函数能够初始化虚指针,让其指向类所对应的虚表
  4. 析构函数能够析构虚指针

  • 多重继承时候,情况会变得不一样,图中可以看到Vertex3d类对象中包含两个虚指针分别指向两个虚表,若在Vertex3d类中重写了虚函数,则会将其替换到对应虚表中的地址,若Vertex3d类中新声明了虚函数,根据继承顺序, 新声明虚函数的地址会附加到第一个虚函数表指针指向的虚函数表的后面。

  • 虚拟继承,虚拟继承解决多重继承中的菱形继承问题。数据布局模型如下图所示,首先,Point3d虚继承Point2d,有一个虚指针指向自己的虚表(这个和单一继承模型分开,可以看最后一个Vertex3d对象就没有自己的虚指针,说明这个只有虚继承情况下才会有),还有一个虚基类指针指向基类(共享数据)

如下一道虚继承的面试题。32位机器上,比较四种情况的对象大小

                                                                                                                                                                                                             

参考:

博客 https://www.cnblogs.com/yinheyi/p/10525543.html

博客 https://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html

书籍 深入理解c++对象模型                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值